mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-08-25 15:59:40 +00:00
Compare commits
97 Commits
0cb2875672
...
bugfix/ass
Author | SHA1 | Date | |
---|---|---|---|
7dc103f904 | |||
e4c41bab90 | |||
0de882d43b | |||
6a737e5d43 | |||
c323d88790 | |||
68153e41ed | |||
6a7174b929 | |||
b6d4084ca7 | |||
2b110b7c91 | |||
066fe4bc95 | |||
85d65b2d96 | |||
a64a9f1d12 | |||
94f9c1beea | |||
a7487f5434 | |||
c0b74162e9 | |||
dfd8c5fa31 | |||
60b8ee8b50 | |||
9d60f913eb | |||
40251b846b | |||
b1b72fa8aa | |||
e8576c8dbe | |||
0c0f26bec7 | |||
1323bceca1 | |||
dd425236f4 | |||
fb506e16c1 | |||
983040135f | |||
0eb4652f26 | |||
460639b681 | |||
35c8a73156 | |||
ce65b068ff | |||
96f107f972 | |||
a3a7937021 | |||
9bf715501b | |||
c65f4a7fab | |||
7af8887d4f | |||
2f89c3486c | |||
5589e5b587 | |||
d3bd363b70 | |||
3a4fce966c | |||
c473325883 | |||
6bdd28ec57 | |||
652163fdae | |||
c8eb07c166 | |||
518e9c8914 | |||
9754b3b589 | |||
327be5aa54 | |||
b12903059d | |||
729b0caac3 | |||
ebfdfe1762 | |||
631766e0ec | |||
a55ff24257 | |||
0ad562b6ce | |||
076c80fe44 | |||
d12b4c0c65 | |||
22c8c54fab | |||
95300071e9 | |||
06f00da02c | |||
7876af9756 | |||
fe2f4a872b | |||
c9b8fbb0c2 | |||
7b5b40a03c | |||
8522c0bbc3 | |||
c6729f476f | |||
fc70669f1d | |||
f03c28f7fd | |||
6ec972a520 | |||
62f67f5a5f | |||
3e634dc7a2 | |||
8e303af0d7 | |||
4ef4858cee | |||
9a203b2fd9 | |||
39c5fd1bca | |||
308eb65d46 | |||
d65f9ceea9 | |||
f539b0ac8d | |||
5a3cf93748 | |||
e740652507 | |||
c60078c96a | |||
903c5dd29b | |||
df39fca050 | |||
f832c5d884 | |||
fa930571dc | |||
acefe7b355 | |||
b223194950 | |||
466f5b89c7 | |||
de5d8df01c | |||
5218641705 | |||
ab6a6851f2 | |||
beb33e37fa | |||
3bee17c574 | |||
f4b5c6fb52 | |||
086f3cedf8 | |||
035c03c6b2 | |||
cf1b34ee0a | |||
5663e2084e | |||
3cd0125310 | |||
e0980b324c |
4
assets/icons/search_icon.svg
Normal file
4
assets/icons/search_icon.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.89852 11.08C-0.632759 8.54864 -0.632708 4.42983 1.89852 1.8985C4.42985 -0.632833 8.54861 -0.632833 11.0799 1.8985C13.2274 4.04599 13.5526 7.23986 12.0565 9.73392C12.0565 9.73392 11.949 9.91422 12.0942 10.0593C12.9222 10.8872 15.4065 13.3716 15.4065 13.3716C16.0658 14.0308 16.2227 14.9527 15.638 15.5374L15.5374 15.638C14.9527 16.2227 14.0308 16.0658 13.3716 15.4065C13.3716 15.4065 10.8926 12.9275 10.0662 12.1012C9.91414 11.9491 9.73389 12.0566 9.73389 12.0566C7.23988 13.5526 4.04601 13.2275 1.89852 11.08ZM9.88131 9.88133C11.7517 8.01094 11.7516 4.96763 9.88125 3.09724C8.01086 1.22689 4.96755 1.22684 3.09721 3.09724C1.22681 4.96758 1.22681 8.01094 3.09721 9.88133C4.9676 11.7516 8.01086 11.7516 9.88131 9.88133Z" fill="#999999" fill-opacity="0.7"/>
|
||||
<path d="M9.46701 6.10386C9.55406 6.10386 9.64256 6.08674 9.72786 6.05072C10.0686 5.9065 10.228 5.51333 10.0838 5.17253C9.17738 3.03045 6.69729 2.0252 4.55526 2.93164C4.21451 3.07586 4.05509 3.46903 4.1993 3.80983C4.34357 4.15063 4.73664 4.30995 5.07755 4.16579C6.539 3.54737 8.23126 4.23326 8.84962 5.69471C8.95781 5.95031 9.20594 6.10386 9.46701 6.10386Z" fill="#999999" fill-opacity="0.7"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
5
assets/icons/x_delete.svg
Normal file
5
assets/icons/x_delete.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="35" height="35" viewBox="0 0 35 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.5 34.9999C27.1649 34.9999 34.9999 27.1649 34.9999 17.4999C34.9999 7.83499 27.1649 0 17.5 0C7.83499 0 0 7.83499 0 17.5C0 27.1651 7.83499 34.9999 17.5 34.9999Z" fill="#FF6465"/>
|
||||
<path opacity="0.1" d="M4.70804 17.5C4.70804 8.63343 11.3024 1.30805 19.854 0.158115C19.0839 0.0545507 18.2984 0 17.5 0C7.835 0 0 7.835 0 17.5C0 27.1651 7.83499 35 17.4999 35C18.2983 35 19.0839 34.9455 19.8539 34.8419C11.3024 33.6919 4.70804 26.3665 4.70804 17.5Z" fill="black"/>
|
||||
<path d="M21.4229 17.5003L26.0301 12.8931C26.365 12.5582 26.365 12.0152 26.0301 11.6804L23.3197 8.96992C22.9848 8.63503 22.4418 8.63503 22.107 8.96992L17.4997 13.5772L12.8924 8.96992C12.5576 8.63503 12.0146 8.63503 11.6798 8.96992L8.96931 11.6804C8.63442 12.0153 8.63442 12.5582 8.96931 12.8931L13.5766 17.5003L8.96931 22.1076C8.63442 22.4425 8.63442 22.9855 8.96931 23.3204L11.6798 26.0308C12.0146 26.3657 12.5576 26.3657 12.8924 26.0308L17.4997 21.4235L22.1071 26.0308C22.442 26.3657 22.9849 26.3657 23.3198 26.0308L26.0302 23.3204C26.3651 22.9855 26.3651 22.4425 26.0302 22.1076L21.4229 17.5003Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
9
assets/images/4_sceen_switch.svg
Normal file
9
assets/images/4_sceen_switch.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M38.0142 39.2553L35.3682 40H20.9308H19.9999H19.0691H1.24111C0.555643 40 0 39.4444 0 38.7589V1.24111C0 0.555643 0.555643 0 1.24111 0H19.0682H20.1226H20.9543H35.4255L38.2625 1.24111C38.9479 1.24111 39.5036 1.79675 39.5036 2.48221L39.2553 38.0142C39.2553 38.6997 38.6997 39.2553 38.0142 39.2553Z" fill="#E9E9E9"/>
|
||||
<path d="M38.7585 0H35.0352C35.7206 0 36.2763 0.555643 36.2763 1.24111V38.7589C36.2763 39.4444 35.7206 40 35.0352 40H38.7585C39.4439 40 39.9996 39.4444 39.9996 38.7589V1.24111C39.9996 0.555643 39.4439 0 38.7585 0Z" fill="#D1D1D1"/>
|
||||
<path opacity="0.6" d="M12.0283 31.8319V33.3212C12.0283 34.0067 11.6086 34.5623 11.0908 34.5623H6.96582C6.44804 34.5623 6.02832 34.0067 6.02832 33.3212V31.8319C6.02832 31.1465 6.44804 30.5908 6.96582 30.5908H11.0908C11.6086 30.5908 12.0283 31.1465 12.0283 31.8319Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path opacity="0.6" d="M12.0283 7.24109V8.73042C12.0283 9.41588 11.6086 9.97153 11.0908 9.97153H6.96582C6.44804 9.97153 6.02832 9.41588 6.02832 8.73042V7.24109C6.02832 6.55563 6.44804 5.99998 6.96582 5.99998H11.0908C11.6086 5.99998 12.0283 6.55563 12.0283 7.24109Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path opacity="0.6" d="M26.0283 31.8319V33.3212C26.0283 34.0067 26.448 34.5623 26.9658 34.5623H31.0908C31.6086 34.5623 32.0283 34.0067 32.0283 33.3212V31.8319C32.0283 31.1465 31.6086 30.5908 31.0908 30.5908H26.9658C26.448 30.5908 26.0283 31.1465 26.0283 31.8319Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path opacity="0.6" d="M26.0283 7.24109V8.73042C26.0283 9.41588 26.448 9.97153 26.9658 9.97153H31.0908C31.6086 9.97153 32.0283 9.41588 32.0283 8.73042V7.24109C32.0283 6.55563 31.6086 5.99998 31.0908 5.99998H26.9658C26.448 5.99998 26.0283 6.55563 26.0283 7.24109Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path d="M19.0693 0H20.931V40H19.0693V0Z" fill="#D1D1D1"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
12
assets/images/6_sceen_switch.svg
Normal file
12
assets/images/6_sceen_switch.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M38.0142 39.2553L35.3682 40H20.9308H19.9999H19.0691H1.24111C0.555643 40 0 39.4444 0 38.7589V1.24111C0 0.555651 0.555643 7.62939e-06 1.24111 7.62939e-06H19.0682H20.1226H20.9543H35.4255L38.2625 1.24111C38.9479 1.24111 39.5036 1.79676 39.5036 2.48222L39.2553 38.0142C39.2553 38.6997 38.6997 39.2553 38.0142 39.2553Z" fill="#E9E9E9"/>
|
||||
<path d="M38.7585 0H35.0352C35.7206 0 36.2763 0.555643 36.2763 1.24111V38.7589C36.2763 39.4444 35.7206 40 35.0352 40H38.7585C39.4439 40 39.9996 39.4444 39.9996 38.7589V1.24111C39.9996 0.555643 39.4439 0 38.7585 0Z" fill="#D1D1D1"/>
|
||||
<path opacity="0.6" d="M8.64062 31.8319V33.3212C8.64062 34.0067 8.22091 34.5623 7.70312 34.5623H3.57813C3.06034 34.5623 2.64062 34.0067 2.64062 33.3212V31.8319C2.64062 31.1464 3.06034 30.5908 3.57813 30.5908H7.70312C8.22091 30.5908 8.64062 31.1464 8.64062 31.8319Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path opacity="0.6" d="M8.64062 7.24109V8.73042C8.64062 9.41588 8.22091 9.97152 7.70312 9.97152H3.57813C3.06034 9.97152 2.64062 9.41588 2.64062 8.73042V7.24109C2.64062 6.55563 3.06034 5.99998 3.57813 5.99998H7.70312C8.22091 5.99998 8.64062 6.55563 8.64062 7.24109Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path opacity="0.6" d="M27.6406 31.8319V33.3212C27.6406 34.0067 28.0603 34.5623 28.5781 34.5623H32.7031C33.2209 34.5623 33.6406 34.0067 33.6406 33.3212V31.8319C33.6406 31.1464 33.2209 30.5908 32.7031 30.5908H28.5781C28.0603 30.5908 27.6406 31.1464 27.6406 31.8319Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path opacity="0.6" d="M27.6406 7.24109V8.73042C27.6406 9.41588 28.0603 9.97152 28.5781 9.97152H32.7031C33.2209 9.97152 33.6406 9.41588 33.6406 8.73042V7.24109C33.6406 6.55563 33.2209 5.99998 32.7031 5.99998H28.5781C28.0603 5.99998 27.6406 6.55563 27.6406 7.24109Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path opacity="0.6" d="M15.0625 31.8319V33.3212C15.0625 34.0067 15.4822 34.5623 16 34.5623H20.125C20.6428 34.5623 21.0625 34.0067 21.0625 33.3212V31.8319C21.0625 31.1464 20.6428 30.5908 20.125 30.5908H16C15.4822 30.5908 15.0625 31.1464 15.0625 31.8319Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path opacity="0.6" d="M15.0625 7.24109V8.73042C15.0625 9.41588 15.4822 9.97152 16 9.97152H20.125C20.6428 9.97152 21.0625 9.41588 21.0625 8.73042V7.24109C21.0625 6.55563 20.6428 5.99998 20.125 5.99998H16C15.4822 5.99998 15.0625 6.55563 15.0625 7.24109Z" fill="#023DFE" fill-opacity="0.5"/>
|
||||
<path d="M23.125 0H24.9867V40H23.125V0Z" fill="#D1D1D1"/>
|
||||
<path d="M11.1719 0H13.0335V40H11.1719V0Z" fill="#D1D1D1"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
@ -1,22 +1,9 @@
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/firebase_options.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
|
||||
import 'package:syncrow_web/services/locator.dart';
|
||||
import 'package:syncrow_web/utils/app_routes.dart';
|
||||
import 'package:syncrow_web/utils/constants/routes_const.dart';
|
||||
import 'package:syncrow_web/utils/navigation_service.dart';
|
||||
import 'package:syncrow_web/utils/theme/theme.dart';
|
||||
import 'package:syncrow_web/syncrow_app.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
try {
|
||||
@ -33,59 +20,5 @@ Future<void> main() async {
|
||||
);
|
||||
initialSetup();
|
||||
} catch (_) {}
|
||||
runApp(MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
MyApp({super.key});
|
||||
|
||||
final GoRouter _router = GoRouter(
|
||||
initialLocation: RoutesConst.auth,
|
||||
routes: AppRoutes.getRoutes(),
|
||||
redirect: (context, state) async {
|
||||
final checkToken = await AuthBloc.getTokenAndValidate();
|
||||
final loggedIn = checkToken == 'Success';
|
||||
final goingToLogin = state.uri.toString() == RoutesConst.auth;
|
||||
|
||||
if (!loggedIn && !goingToLogin) return RoutesConst.auth;
|
||||
if (loggedIn && goingToLogin) return RoutesConst.home;
|
||||
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<CreateRoutineBloc>(
|
||||
create: (context) => CreateRoutineBloc(),
|
||||
),
|
||||
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||
BlocProvider<VisitorPasswordBloc>(
|
||||
create: (context) => VisitorPasswordBloc(),
|
||||
),
|
||||
BlocProvider<RoutineBloc>(
|
||||
create: (context) => RoutineBloc(),
|
||||
),
|
||||
BlocProvider<SpaceTreeBloc>(
|
||||
create: (context) => SpaceTreeBloc(),
|
||||
),
|
||||
],
|
||||
child: MaterialApp.router(
|
||||
debugShowCheckedModeBanner: false,
|
||||
scrollBehavior: const MaterialScrollBehavior().copyWith(
|
||||
dragDevices: {
|
||||
PointerDeviceKind.mouse,
|
||||
PointerDeviceKind.touch,
|
||||
PointerDeviceKind.stylus,
|
||||
PointerDeviceKind.unknown,
|
||||
},
|
||||
),
|
||||
key: NavigationService.navigatorKey,
|
||||
// scaffoldMessengerKey: NavigationService.snackbarKey,
|
||||
theme: myTheme,
|
||||
routerConfig: _router,
|
||||
));
|
||||
}
|
||||
runApp(const SyncrowApp());
|
||||
}
|
||||
|
@ -1,22 +1,9 @@
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/firebase_options.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
|
||||
import 'package:syncrow_web/services/locator.dart';
|
||||
import 'package:syncrow_web/utils/app_routes.dart';
|
||||
import 'package:syncrow_web/utils/constants/routes_const.dart';
|
||||
import 'package:syncrow_web/utils/navigation_service.dart';
|
||||
import 'package:syncrow_web/utils/theme/theme.dart';
|
||||
import 'package:syncrow_web/syncrow_app.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
try {
|
||||
@ -33,59 +20,5 @@ Future<void> main() async {
|
||||
);
|
||||
initialSetup();
|
||||
} catch (_) {}
|
||||
runApp(MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
MyApp({super.key});
|
||||
|
||||
final GoRouter _router = GoRouter(
|
||||
initialLocation: RoutesConst.auth,
|
||||
routes: AppRoutes.getRoutes(),
|
||||
redirect: (context, state) async {
|
||||
final checkToken = await AuthBloc.getTokenAndValidate();
|
||||
final loggedIn = checkToken == 'Success';
|
||||
final goingToLogin = state.uri.toString() == RoutesConst.auth;
|
||||
|
||||
if (!loggedIn && !goingToLogin) return RoutesConst.auth;
|
||||
if (loggedIn && goingToLogin) return RoutesConst.home;
|
||||
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<CreateRoutineBloc>(
|
||||
create: (context) => CreateRoutineBloc(),
|
||||
),
|
||||
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||
BlocProvider<VisitorPasswordBloc>(
|
||||
create: (context) => VisitorPasswordBloc(),
|
||||
),
|
||||
BlocProvider<RoutineBloc>(
|
||||
create: (context) => RoutineBloc(),
|
||||
),
|
||||
BlocProvider<SpaceTreeBloc>(
|
||||
create: (context) => SpaceTreeBloc(),
|
||||
),
|
||||
],
|
||||
child: MaterialApp.router(
|
||||
debugShowCheckedModeBanner: false,
|
||||
scrollBehavior: const MaterialScrollBehavior().copyWith(
|
||||
dragDevices: {
|
||||
PointerDeviceKind.mouse,
|
||||
PointerDeviceKind.touch,
|
||||
PointerDeviceKind.stylus,
|
||||
PointerDeviceKind.unknown,
|
||||
},
|
||||
),
|
||||
key: NavigationService.navigatorKey,
|
||||
// scaffoldMessengerKey: NavigationService.snackbarKey,
|
||||
theme: myTheme,
|
||||
routerConfig: _router,
|
||||
));
|
||||
}
|
||||
runApp(const SyncrowApp());
|
||||
}
|
||||
|
@ -1,26 +1,16 @@
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/firebase_options.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
|
||||
import 'package:syncrow_web/services/locator.dart';
|
||||
import 'package:syncrow_web/utils/app_routes.dart';
|
||||
import 'package:syncrow_web/utils/constants/routes_const.dart';
|
||||
import 'package:syncrow_web/utils/navigation_service.dart';
|
||||
import 'package:syncrow_web/utils/theme/theme.dart';
|
||||
import 'package:syncrow_web/syncrow_app.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
try {
|
||||
const environment = String.fromEnvironment('FLAVOR', defaultValue: 'staging');
|
||||
const environment = String.fromEnvironment(
|
||||
'FLAVOR',
|
||||
defaultValue: 'staging',
|
||||
);
|
||||
await dotenv.load(fileName: '.env.$environment');
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Firebase.initializeApp(
|
||||
@ -30,59 +20,5 @@ Future<void> main() async {
|
||||
);
|
||||
initialSetup();
|
||||
} catch (_) {}
|
||||
runApp(MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
MyApp({super.key});
|
||||
|
||||
final GoRouter _router = GoRouter(
|
||||
initialLocation: RoutesConst.auth,
|
||||
routes: AppRoutes.getRoutes(),
|
||||
redirect: (context, state) async {
|
||||
final checkToken = await AuthBloc.getTokenAndValidate();
|
||||
final loggedIn = checkToken == 'Success';
|
||||
final goingToLogin = state.uri.toString() == RoutesConst.auth;
|
||||
|
||||
if (!loggedIn && !goingToLogin) return RoutesConst.auth;
|
||||
if (loggedIn && goingToLogin) return RoutesConst.home;
|
||||
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<CreateRoutineBloc>(
|
||||
create: (context) => CreateRoutineBloc(),
|
||||
),
|
||||
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||
BlocProvider<VisitorPasswordBloc>(
|
||||
create: (context) => VisitorPasswordBloc(),
|
||||
),
|
||||
BlocProvider<RoutineBloc>(
|
||||
create: (context) => RoutineBloc(),
|
||||
),
|
||||
BlocProvider<SpaceTreeBloc>(
|
||||
create: (context) => SpaceTreeBloc(),
|
||||
),
|
||||
],
|
||||
child: MaterialApp.router(
|
||||
debugShowCheckedModeBanner: false,
|
||||
scrollBehavior: const MaterialScrollBehavior().copyWith(
|
||||
dragDevices: {
|
||||
PointerDeviceKind.mouse,
|
||||
PointerDeviceKind.touch,
|
||||
PointerDeviceKind.stylus,
|
||||
PointerDeviceKind.unknown,
|
||||
},
|
||||
),
|
||||
key: NavigationService.navigatorKey,
|
||||
// scaffoldMessengerKey: NavigationService.snackbarKey,
|
||||
theme: myTheme,
|
||||
routerConfig: _router,
|
||||
));
|
||||
}
|
||||
runApp(const SyncrowApp());
|
||||
}
|
||||
|
@ -0,0 +1,63 @@
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/data/services/remote_calendar_service.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/domain/LoadEventsParam.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_service.dart';
|
||||
|
||||
class MemoryCalendarService implements CalendarSystemService {
|
||||
final Map<String, CalendarEventsResponse> _eventsCache = {};
|
||||
|
||||
@override
|
||||
Future<CalendarEventsResponse> getCalendarEvents({
|
||||
required LoadEventsParam params,
|
||||
}) async {
|
||||
final key = params.generateKey();
|
||||
|
||||
return _eventsCache[key]!;
|
||||
}
|
||||
|
||||
void setEvents(
|
||||
LoadEventsParam param,
|
||||
CalendarEventsResponse events,
|
||||
) {
|
||||
final key = param.generateKey();
|
||||
_eventsCache[key] = events;
|
||||
}
|
||||
|
||||
void addEvent(LoadEventsParam param, CalendarEventsResponse event) {
|
||||
final key = param.generateKey();
|
||||
|
||||
_eventsCache[key] = event;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_eventsCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
class MemoryCalendarServiceWithRemoteFallback implements CalendarSystemService {
|
||||
final MemoryCalendarService memoryService;
|
||||
final RemoteCalendarService remoteService;
|
||||
|
||||
MemoryCalendarServiceWithRemoteFallback({
|
||||
required this.memoryService,
|
||||
required this.remoteService,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<CalendarEventsResponse> getCalendarEvents({
|
||||
required LoadEventsParam params,
|
||||
}) async {
|
||||
final key = params.generateKey();
|
||||
final doesExistInMemory = memoryService._eventsCache.containsKey(key);
|
||||
|
||||
if (doesExistInMemory) {
|
||||
return memoryService.getCalendarEvents(params: params);
|
||||
} else {
|
||||
final remoteResult =
|
||||
await remoteService.getCalendarEvents(params: params);
|
||||
memoryService.setEvents(params, remoteResult);
|
||||
|
||||
return remoteResult;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/domain/LoadEventsParam.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_service.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
@ -13,147 +14,21 @@ class RemoteCalendarService implements CalendarSystemService {
|
||||
|
||||
@override
|
||||
Future<CalendarEventsResponse> getCalendarEvents({
|
||||
required String spaceId,
|
||||
required LoadEventsParam params,
|
||||
}) async {
|
||||
final month = params.startDate.month.toString().padLeft(2, '0');
|
||||
final year = params.startDate.year.toString();
|
||||
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
path: ApiEndpoints.getCalendarEvents,
|
||||
queryParameters: {
|
||||
'spaceId': spaceId,
|
||||
},
|
||||
return await _httpService.get<CalendarEventsResponse>(
|
||||
path: ApiEndpoints.getBookings
|
||||
.replaceAll('{mm}', month)
|
||||
.replaceAll('{yyyy}', year)
|
||||
.replaceAll('{space}', params.id),
|
||||
expectedResponseModel: (json) {
|
||||
return CalendarEventsResponse.fromJson(
|
||||
json as Map<String, dynamic>,
|
||||
);
|
||||
return CalendarEventsResponse.fromJson(json as Map<String, dynamic>);
|
||||
},
|
||||
);
|
||||
|
||||
return CalendarEventsResponse.fromJson(response as Map<String, dynamic>);
|
||||
} on DioException catch (e) {
|
||||
final responseData = e.response?.data;
|
||||
if (responseData is Map<String, dynamic>) {
|
||||
final errorMessage = responseData['error']?['message'] as String? ??
|
||||
responseData['message'] as String? ??
|
||||
_defaultErrorMessage;
|
||||
throw APIException(errorMessage);
|
||||
}
|
||||
throw APIException(_defaultErrorMessage);
|
||||
} catch (e) {
|
||||
throw APIException('$_defaultErrorMessage: ${e.toString()}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FakeRemoteCalendarService implements CalendarSystemService {
|
||||
const FakeRemoteCalendarService(this._httpService, {this.useDummy = false});
|
||||
|
||||
final HTTPService _httpService;
|
||||
final bool useDummy;
|
||||
static const _defaultErrorMessage = 'Failed to load Calendar';
|
||||
|
||||
@override
|
||||
Future<CalendarEventsResponse> getCalendarEvents({
|
||||
required String spaceId,
|
||||
}) async {
|
||||
if (useDummy) {
|
||||
final dummyJson = {
|
||||
'statusCode': 200,
|
||||
'message': 'Successfully fetched all bookings',
|
||||
'data': [
|
||||
{
|
||||
'uuid': 'd4553fa6-a0c9-4f42-81c9-99a13a57bf80',
|
||||
'date': '2025-07-11T10:22:00.626Z',
|
||||
'startTime': '09:00:00',
|
||||
'endTime': '12:00:00',
|
||||
'cost': 10,
|
||||
'user': {
|
||||
'uuid': '784394ff-3197-4c39-9f07-48dc44920b1e',
|
||||
'firstName': 'salsabeel',
|
||||
'lastName': 'abuzaid',
|
||||
'email': 'test@test.com',
|
||||
'companyName': null
|
||||
},
|
||||
'space': {
|
||||
'uuid': '000f4d81-43e4-4ad7-865c-0f8b04b7081e',
|
||||
'spaceName': '2(1)'
|
||||
}
|
||||
},
|
||||
{
|
||||
'uuid': 'e9b27af0-b963-4d98-9657-454c4ba78561',
|
||||
'date': '2025-07-11T10:22:00.626Z',
|
||||
'startTime': '12:00:00',
|
||||
'endTime': '13:00:00',
|
||||
'cost': 10,
|
||||
'user': {
|
||||
'uuid': '784394ff-3197-4c39-9f07-48dc44920b1e',
|
||||
'firstName': 'salsabeel',
|
||||
'lastName': 'abuzaid',
|
||||
'email': 'test@test.com',
|
||||
'companyName': null
|
||||
},
|
||||
'space': {
|
||||
'uuid': '000f4d81-43e4-4ad7-865c-0f8b04b7081e',
|
||||
'spaceName': '2(1)'
|
||||
}
|
||||
},
|
||||
{
|
||||
'uuid': 'e9b27af0-b963-4d98-9657-454c4ba78561',
|
||||
'date': '2025-07-13T10:22:00.626Z',
|
||||
'startTime': '15:30:00',
|
||||
'endTime': '19:00:00',
|
||||
'cost': 20,
|
||||
'user': {
|
||||
'uuid': '784394ff-3197-4c39-9f07-48dc44920b1e',
|
||||
'firstName': 'salsabeel',
|
||||
'lastName': 'abuzaid',
|
||||
'email': 'test@test.com',
|
||||
'companyName': null
|
||||
},
|
||||
'space': {
|
||||
'uuid': '000f4d81-43e4-4ad7-865c-0f8b04b7081e',
|
||||
'spaceName': '2(1)'
|
||||
}
|
||||
}
|
||||
],
|
||||
'success': true
|
||||
};
|
||||
final response = CalendarEventsResponse.fromJson(dummyJson);
|
||||
|
||||
// Filter events by spaceId
|
||||
final filteredData = response.data.where((event) {
|
||||
return event.space.uuid == spaceId;
|
||||
}).toList();
|
||||
print('Filtering events for spaceId: $spaceId');
|
||||
print('Found ${filteredData.length} matching events');
|
||||
return filteredData.isNotEmpty
|
||||
? CalendarEventsResponse(
|
||||
statusCode: response.statusCode,
|
||||
message: response.message,
|
||||
data: filteredData,
|
||||
success: response.success,
|
||||
)
|
||||
: CalendarEventsResponse(
|
||||
statusCode: 404,
|
||||
message: 'No events found for spaceId: $spaceId',
|
||||
data: [],
|
||||
success: false,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
path: ApiEndpoints.getCalendarEvents,
|
||||
queryParameters: {
|
||||
'spaceId': spaceId,
|
||||
},
|
||||
expectedResponseModel: (json) {
|
||||
return CalendarEventsResponse.fromJson(
|
||||
json as Map<String, dynamic>,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return CalendarEventsResponse.fromJson(response as Map<String, dynamic>);
|
||||
} on DioException catch (e) {
|
||||
final responseData = e.response?.data;
|
||||
if (responseData is Map<String, dynamic>) {
|
||||
|
@ -0,0 +1,34 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class LoadEventsParam extends Equatable {
|
||||
final DateTime startDate;
|
||||
final DateTime endDate;
|
||||
final String id;
|
||||
|
||||
const LoadEventsParam({
|
||||
required this.startDate,
|
||||
required this.endDate,
|
||||
required this.id,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [startDate, endDate, id];
|
||||
|
||||
LoadEventsParam copyWith({
|
||||
DateTime? startDate,
|
||||
DateTime? endDate,
|
||||
String? id,
|
||||
}) {
|
||||
return LoadEventsParam(
|
||||
startDate: startDate ?? this.startDate,
|
||||
endDate: endDate ?? this.endDate,
|
||||
id: id ?? this.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension KeyGenerator on LoadEventsParam {
|
||||
String generateKey() {
|
||||
return '$id-${startDate.year}-${startDate.month.toString().padLeft(2, '0')}';
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/domain/LoadEventsParam.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart';
|
||||
|
||||
abstract class CalendarSystemService {
|
||||
Future<CalendarEventsResponse> getCalendarEvents({
|
||||
required String spaceId,
|
||||
required LoadEventsParam params,
|
||||
});
|
||||
}
|
||||
|
@ -2,37 +2,48 @@ import 'dart:async';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:calendar_view/calendar_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/domain/LoadEventsParam.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_service.dart';
|
||||
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/data/services/memory_bookable_space_service.dart';
|
||||
part 'events_event.dart';
|
||||
part 'events_state.dart';
|
||||
|
||||
class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
|
||||
final EventController eventController = EventController();
|
||||
EventController eventController = EventController();
|
||||
final CalendarSystemService calendarService;
|
||||
|
||||
CalendarEventsBloc({required this.calendarService}) : super(EventsInitial()) {
|
||||
CalendarEventsBloc({
|
||||
required this.calendarService,
|
||||
}) : super(EventsInitial()) {
|
||||
on<LoadEvents>(_onLoadEvents);
|
||||
on<AddEvent>(_onAddEvent);
|
||||
on<StartTimer>(_onStartTimer);
|
||||
on<DisposeResources>(_onDisposeResources);
|
||||
on<GoToWeek>(_onGoToWeek);
|
||||
on<ResetEvents>(_onResetEvents);
|
||||
}
|
||||
|
||||
Future<void> _onLoadEvents(
|
||||
LoadEvents event,
|
||||
Emitter<CalendarEventState> emit,
|
||||
) async {
|
||||
final param = event.param;
|
||||
final month = param.endDate.month;
|
||||
final year = param.endDate.year;
|
||||
final spaceId = param.id;
|
||||
|
||||
emit(EventsLoading());
|
||||
try {
|
||||
final response = await calendarService.getCalendarEvents(
|
||||
spaceId: event.spaceId,
|
||||
);
|
||||
final events =
|
||||
response.data.map<CalendarEventData>(_toCalendarEventData).toList();
|
||||
final response = await calendarService.getCalendarEvents(params: param);
|
||||
|
||||
final events = response.data.map(_toCalendarEventData).toList();
|
||||
eventController.addAll(events);
|
||||
emit(EventsLoaded(events: events));
|
||||
emit(EventsLoaded(
|
||||
events: events,
|
||||
spaceId: spaceId,
|
||||
month: month,
|
||||
year: year,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(EventsError('Failed to load events'));
|
||||
}
|
||||
@ -40,16 +51,19 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
|
||||
|
||||
void _onAddEvent(AddEvent event, Emitter<CalendarEventState> emit) {
|
||||
eventController.add(event.event);
|
||||
|
||||
if (state is EventsLoaded) {
|
||||
final loaded = state as EventsLoaded;
|
||||
|
||||
emit(EventsLoaded(
|
||||
events: [...eventController.events],
|
||||
spaceId: loaded.spaceId,
|
||||
month: loaded.month,
|
||||
year: loaded.year,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void _onStartTimer(StartTimer event, Emitter<CalendarEventState> emit) {}
|
||||
|
||||
void _onDisposeResources(
|
||||
DisposeResources event, Emitter<CalendarEventState> emit) {
|
||||
eventController.dispose();
|
||||
@ -61,6 +75,9 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
|
||||
final newWeekDays = _getWeekDays(event.weekDate);
|
||||
emit(EventsLoaded(
|
||||
events: loaded.events,
|
||||
spaceId: loaded.spaceId,
|
||||
month: loaded.month,
|
||||
year: loaded.year,
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -90,14 +107,13 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
|
||||
);
|
||||
|
||||
return CalendarEventData(
|
||||
date: startTime,
|
||||
date: startTime,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
title:
|
||||
'${booking.space.spaceName} - ${booking.user.firstName} ${booking.user.lastName}',
|
||||
title: '${booking.user.firstName} ${booking.user.lastName}',
|
||||
description: 'Cost: ${booking.cost}',
|
||||
color: Colors.blue,
|
||||
event: booking,
|
||||
event: booking,
|
||||
);
|
||||
}
|
||||
|
||||
@ -112,4 +128,18 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
|
||||
eventController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _onResetEvents(
|
||||
ResetEvents event,
|
||||
Emitter<CalendarEventState> emit,
|
||||
) {
|
||||
if (calendarService is MemoryCalendarServiceWithRemoteFallback) {
|
||||
(calendarService as MemoryCalendarServiceWithRemoteFallback)
|
||||
.memoryService
|
||||
.clear();
|
||||
}
|
||||
eventController.dispose();
|
||||
eventController = EventController();
|
||||
emit(EventsInitial());
|
||||
}
|
||||
}
|
||||
|
@ -6,17 +6,11 @@ abstract class CalendarEventsEvent {
|
||||
}
|
||||
|
||||
class LoadEvents extends CalendarEventsEvent {
|
||||
final String spaceId;
|
||||
final DateTime weekStart;
|
||||
final DateTime weekEnd;
|
||||
|
||||
const LoadEvents({
|
||||
required this.spaceId,
|
||||
required this.weekStart,
|
||||
required this.weekEnd,
|
||||
});
|
||||
final LoadEventsParam param;
|
||||
const LoadEvents(this.param);
|
||||
}
|
||||
|
||||
|
||||
class AddEvent extends CalendarEventsEvent {
|
||||
final CalendarEventData event;
|
||||
const AddEvent(this.event);
|
||||
@ -35,3 +29,10 @@ class CheckWeekHasEvents extends CalendarEventsEvent {
|
||||
final DateTime weekStart;
|
||||
const CheckWeekHasEvents(this.weekStart);
|
||||
}
|
||||
|
||||
class ResetEvents extends CalendarEventsEvent {
|
||||
const ResetEvents();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
@ -7,11 +7,17 @@ class EventsInitial extends CalendarEventState {}
|
||||
|
||||
class EventsLoading extends CalendarEventState {}
|
||||
|
||||
class EventsLoaded extends CalendarEventState {
|
||||
final class EventsLoaded extends CalendarEventState {
|
||||
final List<CalendarEventData> events;
|
||||
final String spaceId;
|
||||
final int month;
|
||||
final int year;
|
||||
|
||||
EventsLoaded({
|
||||
required this.events,
|
||||
required this.spaceId,
|
||||
required this.month,
|
||||
required this.year,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,15 @@
|
||||
// booking_page.dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:calendar_view/calendar_view.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/data/services/remote_calendar_service.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/domain/LoadEventsParam.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_event.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_state.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/selected_bookable_space_bloc/selected_bookable_space_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/data/services/memory_bookable_space_service.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/booking_sidebar.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/custom_calendar_page.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/icon_text_button.dart';
|
||||
@ -24,7 +27,33 @@ class BookingPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _BookingPageState extends State<BookingPage> {
|
||||
late final EventController _eventController;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(create: (_) => SelectedBookableSpaceBloc()),
|
||||
BlocProvider(create: (_) => DateSelectionBloc()),
|
||||
BlocProvider(
|
||||
create: (_) => CalendarEventsBloc(
|
||||
calendarService: MemoryCalendarServiceWithRemoteFallback(
|
||||
remoteService: RemoteCalendarService(HTTPService()),
|
||||
memoryService: MemoryCalendarService(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: _BookingPageContent(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BookingPageContent extends StatefulWidget {
|
||||
@override
|
||||
State<_BookingPageContent> createState() => _BookingPageContentState();
|
||||
}
|
||||
|
||||
class _BookingPageContentState extends State<_BookingPageContent> {
|
||||
late EventController _eventController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -38,7 +67,7 @@ class _BookingPageState extends State<BookingPage> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _dispatchLoadEvents(BuildContext context) {
|
||||
void _loadEvents(BuildContext context) {
|
||||
final selectedRoom =
|
||||
context.read<SelectedBookableSpaceBloc>().state.selectedBookableSpace;
|
||||
final dateState = context.read<DateSelectionBloc>().state;
|
||||
@ -46,9 +75,11 @@ class _BookingPageState extends State<BookingPage> {
|
||||
if (selectedRoom != null) {
|
||||
context.read<CalendarEventsBloc>().add(
|
||||
LoadEvents(
|
||||
spaceId: selectedRoom.uuid,
|
||||
weekStart: dateState.weekStart,
|
||||
weekEnd: dateState.weekStart.add(const Duration(days: 6)),
|
||||
LoadEventsParam(
|
||||
startDate: dateState.weekStart,
|
||||
endDate: dateState.weekStart.add(const Duration(days: 6)),
|
||||
id: selectedRoom.uuid,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -56,182 +87,170 @@ class _BookingPageState extends State<BookingPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(create: (_) => SelectedBookableSpaceBloc()),
|
||||
BlocProvider(create: (_) => DateSelectionBloc()),
|
||||
BlocProvider(
|
||||
create: (_) => CalendarEventsBloc(
|
||||
calendarService:
|
||||
FakeRemoteCalendarService(HTTPService(), useDummy: true),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: Builder(
|
||||
builder: (context) =>
|
||||
BlocListener<CalendarEventsBloc, CalendarEventState>(
|
||||
listenWhen: (prev, curr) => curr is EventsLoaded,
|
||||
return BlocListener<SelectedBookableSpaceBloc, SelectedBookableSpaceState>(
|
||||
listener: (context, state) {
|
||||
if (state.selectedBookableSpace != null) {
|
||||
// Reset events and clear cache when room changes
|
||||
context.read<CalendarEventsBloc>().add(ResetEvents());
|
||||
_loadEvents(context);
|
||||
}
|
||||
},
|
||||
child: BlocListener<DateSelectionBloc, DateSelectionState>(
|
||||
listener: (context, state) {
|
||||
_loadEvents(context);
|
||||
},
|
||||
child: BlocListener<CalendarEventsBloc, CalendarEventState>(
|
||||
listener: (context, state) {
|
||||
if (state is EventsLoaded) {
|
||||
_eventController.removeWhere((_) => true);
|
||||
_eventController.addAll(state.events);
|
||||
}
|
||||
},
|
||||
child: BlocListener<SelectedBookableSpaceBloc,
|
||||
SelectedBookableSpaceState>(
|
||||
listener: (context, state) => _dispatchLoadEvents(context),
|
||||
child: BlocListener<DateSelectionBloc, DateSelectionState>(
|
||||
listener: (context, state) => _dispatchLoadEvents(context),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.whiteColors,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: ColorsManager.blackColor.withOpacity(0.1),
|
||||
offset: const Offset(3, 0),
|
||||
blurRadius: 6,
|
||||
spreadRadius: 0,
|
||||
),
|
||||
],
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.whiteColors,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: ColorsManager.blackColor.withOpacity(0.1),
|
||||
offset: const Offset(3, 0),
|
||||
blurRadius: 6,
|
||||
spreadRadius: 0,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: BlocBuilder<SelectedBookableSpaceBloc,
|
||||
SelectedBookableSpaceState>(
|
||||
builder: (context, state) {
|
||||
return BookingSidebar(
|
||||
onRoomSelected: (selectedRoom) {
|
||||
context
|
||||
.read<SelectedBookableSpaceBloc>()
|
||||
.add(SelectBookableSpace(selectedRoom));
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: BlocBuilder<DateSelectionBloc,
|
||||
DateSelectionState>(
|
||||
builder: (context, dateState) {
|
||||
return CustomCalendarPage(
|
||||
selectedDate: dateState.selectedDate,
|
||||
onDateChanged: (day, month, year) {
|
||||
final newDate = DateTime(year, month, day);
|
||||
context
|
||||
.read<DateSelectionBloc>()
|
||||
.add(SelectDate(newDate));
|
||||
context.read<DateSelectionBloc>().add(
|
||||
SelectDateFromSidebarCalendar(newDate));
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: BlocBuilder<SelectedBookableSpaceBloc,
|
||||
SelectedBookableSpaceState>(
|
||||
builder: (context, state) {
|
||||
return BookingSidebar(
|
||||
onRoomSelected: (selectedRoom) {
|
||||
context
|
||||
.read<SelectedBookableSpaceBloc>()
|
||||
.add(SelectBookableSpace(selectedRoom));
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child:
|
||||
BlocBuilder<DateSelectionBloc, DateSelectionState>(
|
||||
builder: (context, dateState) {
|
||||
return CustomCalendarPage(
|
||||
selectedDate: dateState.selectedDate,
|
||||
onDateChanged: (day, month, year) {
|
||||
final newDate = DateTime(year, month, day);
|
||||
context
|
||||
.read<DateSelectionBloc>()
|
||||
.add(SelectDate(newDate));
|
||||
context.read<DateSelectionBloc>().add(
|
||||
SelectDateFromSidebarCalendar(newDate));
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
SvgTextButton(
|
||||
svgAsset: Assets.homeIcon,
|
||||
label: 'Manage Bookable Spaces',
|
||||
onPressed: () {},
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
SvgTextButton(
|
||||
svgAsset: Assets.groupIcon,
|
||||
label: 'Manage Users',
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
SvgTextButton(
|
||||
svgAsset: Assets.homeIcon,
|
||||
label: 'Manage Bookable Spaces',
|
||||
onPressed: () {},
|
||||
),
|
||||
BlocBuilder<DateSelectionBloc,
|
||||
DateSelectionState>(
|
||||
builder: (context, state) {
|
||||
final weekStart = state.weekStart;
|
||||
final weekEnd =
|
||||
weekStart.add(const Duration(days: 6));
|
||||
return WeekNavigation(
|
||||
weekStart: weekStart,
|
||||
weekEnd: weekEnd,
|
||||
onPreviousWeek: () {
|
||||
context
|
||||
.read<DateSelectionBloc>()
|
||||
.add(PreviousWeek());
|
||||
},
|
||||
onNextWeek: () {
|
||||
context
|
||||
.read<DateSelectionBloc>()
|
||||
.add(NextWeek());
|
||||
},
|
||||
);
|
||||
},
|
||||
const SizedBox(width: 20),
|
||||
SvgTextButton(
|
||||
svgAsset: Assets.groupIcon,
|
||||
label: 'Manage Users',
|
||||
onPressed: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: BlocBuilder<SelectedBookableSpaceBloc,
|
||||
SelectedBookableSpaceState>(
|
||||
builder: (context, roomState) {
|
||||
final selectedRoom =
|
||||
roomState.selectedBookableSpace;
|
||||
return BlocBuilder<DateSelectionBloc,
|
||||
DateSelectionState>(
|
||||
builder: (context, dateState) {
|
||||
return BlocListener<CalendarEventsBloc,
|
||||
CalendarEventState>(
|
||||
listenWhen: (prev, curr) =>
|
||||
curr is EventsLoaded,
|
||||
listener: (context, state) {
|
||||
if (state is EventsLoaded) {
|
||||
_eventController
|
||||
.removeWhere((_) => true);
|
||||
_eventController.addAll(state.events);
|
||||
}
|
||||
},
|
||||
child: WeeklyCalendarPage(
|
||||
startTime: selectedRoom
|
||||
?.bookableConfig.startTime,
|
||||
endTime: selectedRoom
|
||||
?.bookableConfig.endTime,
|
||||
weekStart: dateState.weekStart,
|
||||
selectedDate: dateState.selectedDate,
|
||||
eventController: _eventController,
|
||||
selectedDateFromSideBarCalender: context
|
||||
.watch<DateSelectionBloc>()
|
||||
.state
|
||||
.selectedDateFromSideBarCalender,
|
||||
),
|
||||
BlocBuilder<DateSelectionBloc, DateSelectionState>(
|
||||
builder: (context, state) {
|
||||
final weekStart = state.weekStart;
|
||||
final weekEnd =
|
||||
weekStart.add(const Duration(days: 6));
|
||||
return WeekNavigation(
|
||||
weekStart: weekStart,
|
||||
weekEnd: weekEnd,
|
||||
onPreviousWeek: () {
|
||||
context
|
||||
.read<DateSelectionBloc>()
|
||||
.add(PreviousWeek());
|
||||
},
|
||||
onNextWeek: () {
|
||||
context
|
||||
.read<DateSelectionBloc>()
|
||||
.add(NextWeek());
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: BlocBuilder<SelectedBookableSpaceBloc,
|
||||
SelectedBookableSpaceState>(
|
||||
builder: (context, roomState) {
|
||||
final selectedRoom =
|
||||
roomState.selectedBookableSpace;
|
||||
return BlocBuilder<DateSelectionBloc,
|
||||
DateSelectionState>(
|
||||
builder: (context, dateState) {
|
||||
return BlocBuilder<CalendarEventsBloc,
|
||||
CalendarEventState>(
|
||||
builder: (context, eventState) {
|
||||
return WeeklyCalendarPage(
|
||||
key: ValueKey(
|
||||
selectedRoom?.uuid ?? 'no-room'),
|
||||
startTime: selectedRoom
|
||||
?.bookableConfig.startTime,
|
||||
endTime:
|
||||
selectedRoom?.bookableConfig.endTime,
|
||||
weekStart: dateState.weekStart,
|
||||
selectedDate: dateState.selectedDate,
|
||||
eventController: _eventController,
|
||||
selectedDateFromSideBarCalender: context
|
||||
.watch<DateSelectionBloc>()
|
||||
.state
|
||||
.selectedDateFromSideBarCalender,
|
||||
// isLoading: eventState is EventsLoading,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -72,29 +72,33 @@ class __SidebarContentState extends State<_SidebarContent> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocConsumer<SidebarBloc, SidebarState>(
|
||||
listener: (context, state) {
|
||||
if (state.currentPage == 1 && searchController.text.isNotEmpty) {
|
||||
searchController.clear();
|
||||
}
|
||||
},
|
||||
listener: (context, state) {},
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
const _SidebarHeader(title: 'Spaces'),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.whiteColors,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: ColorsManager.blackColor.withOpacity(0.1),
|
||||
offset: const Offset(0, 4),
|
||||
blurRadius: 4,
|
||||
spreadRadius: 0,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: _SidebarHeader(title: 'Spaces')),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.whiteColors,
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: ColorsManager.blackColor.withOpacity(0.1),
|
||||
offset: const Offset(0, -2),
|
||||
blurRadius: 4,
|
||||
spreadRadius: 0,
|
||||
),
|
||||
BoxShadow(
|
||||
color: ColorsManager.blackColor.withOpacity(0.1),
|
||||
offset: const Offset(0, 2),
|
||||
offset: const Offset(0, 4),
|
||||
blurRadius: 4,
|
||||
spreadRadius: 0,
|
||||
),
|
||||
@ -147,6 +151,7 @@ class __SidebarContentState extends State<_SidebarContent> {
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () {
|
||||
searchController.clear();
|
||||
context.read<SidebarBloc>().add(ResetSearch());
|
||||
},
|
||||
),
|
||||
@ -223,7 +228,7 @@ class _SidebarHeader extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
|
@ -66,18 +66,28 @@ class _CustomCalendarPageState extends State<CustomCalendarPage> {
|
||||
weekdayLabels: const ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'],
|
||||
);
|
||||
|
||||
return CalendarDatePicker2(
|
||||
config: config,
|
||||
value: [_selectedDate],
|
||||
onValueChanged: (dates) {
|
||||
final picked = dates.first;
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
_selectedDate = picked;
|
||||
});
|
||||
widget.onDateChanged(picked.day, picked.month, picked.year);
|
||||
}
|
||||
},
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
color: ColorsManager.textGray,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: CalendarDatePicker2(
|
||||
config: config,
|
||||
value: [_selectedDate],
|
||||
onValueChanged: (dates) {
|
||||
final picked = dates.first;
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
_selectedDate = picked;
|
||||
});
|
||||
widget.onDateChanged(picked.day, picked.month, picked.year);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,15 @@
|
||||
import 'package:calendar_view/calendar_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class EventTileWidget extends StatelessWidget {
|
||||
final List<CalendarEventData<Object?>> events;
|
||||
|
||||
const EventTileWidget({
|
||||
super.key,
|
||||
required this.events,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
@ -18,39 +17,88 @@ class EventTileWidget extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: events.map((event) {
|
||||
final bool isEventEnded =
|
||||
final booking = event.event is CalendarEventBooking
|
||||
? event.event! as CalendarEventBooking
|
||||
: null;
|
||||
|
||||
final companyName = booking?.user.companyName ?? 'Unknown Company';
|
||||
final startTime = DateFormat('hh:mm a').format(event.startTime!);
|
||||
final endTime = DateFormat('hh:mm a').format(event.endTime!);
|
||||
final isEventEnded =
|
||||
event.endTime != null && event.endTime!.isBefore(DateTime.now());
|
||||
|
||||
final duration = event.endTime!.difference(event.startTime!);
|
||||
final bool isLongEnough = duration.inMinutes >= 31;
|
||||
return Expanded(
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(6),
|
||||
padding: const EdgeInsets.all(5),
|
||||
decoration: BoxDecoration(
|
||||
color: isEventEnded
|
||||
? ColorsManager.lightGrayBorderColor
|
||||
: ColorsManager.blue1.withOpacity(0.25),
|
||||
? ColorsManager.grayColor.withOpacity(0.1)
|
||||
: ColorsManager.blue1.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
DateFormat('h:mm a').format(event.startTime!),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
color: Colors.black87,
|
||||
),
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
color: isEventEnded
|
||||
? ColorsManager.grayColor
|
||||
: ColorsManager.secondaryColor,
|
||||
width: 4,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
event.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: isLongEnough
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'$startTime - $endTime',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: isEventEnded
|
||||
? ColorsManager.grayColor.withOpacity(0.9)
|
||||
: ColorsManager.blackColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
event.title,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: isEventEnded
|
||||
? ColorsManager.grayColor
|
||||
: ColorsManager.blackColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
companyName,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: isEventEnded
|
||||
? ColorsManager.grayColor.withOpacity(0.9)
|
||||
: ColorsManager.blackColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text(
|
||||
event.title,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: isEventEnded
|
||||
? ColorsManager.grayColor
|
||||
: ColorsManager.blackColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
|
@ -4,8 +4,7 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class SvgTextButton extends StatelessWidget {
|
||||
final String svgAsset;
|
||||
final double? horizontalPadding;
|
||||
final double? verticalPadding;
|
||||
final EdgeInsets? padding;
|
||||
final String label;
|
||||
final VoidCallback onPressed;
|
||||
final Color backgroundColor;
|
||||
@ -27,8 +26,7 @@ class SvgTextButton extends StatelessWidget {
|
||||
this.svgColor = const Color(0xFF496EFF),
|
||||
this.labelColor = Colors.black,
|
||||
this.borderRadius = 10.0,
|
||||
this.horizontalPadding,
|
||||
this.verticalPadding,
|
||||
this.padding,
|
||||
this.boxShadow = const [
|
||||
BoxShadow(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
@ -47,9 +45,8 @@ class SvgTextButton extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
onTap: onPressed,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: horizontalPadding ?? 24,
|
||||
vertical: verticalPadding ?? 12),
|
||||
padding: padding ??
|
||||
const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
|
@ -21,20 +21,24 @@ class RoomListItem extends StatelessWidget {
|
||||
groupValue: isSelected ? room.uuid : null,
|
||||
visualDensity: const VisualDensity(vertical: -4),
|
||||
onChanged: (value) => onTap(),
|
||||
activeColor: ColorsManager.primaryColor,
|
||||
activeColor: ColorsManager.secondaryColor,
|
||||
title: Text(
|
||||
room.spaceName,
|
||||
maxLines: 2,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
fontWeight: FontWeight.w700,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
fontSize: 12),
|
||||
),
|
||||
subtitle: Text(
|
||||
room.virtualLocation,
|
||||
maxLines: 2,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: ColorsManager.textGray,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -14,26 +14,33 @@ class WeekDayHeader extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
DateFormat('EEE').format(date).toUpperCase(),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 14,
|
||||
color: isSelectedDay ? Colors.blue : Colors.black,
|
||||
return ColoredBox(
|
||||
color: isSelectedDay
|
||||
? ColorsManager.secondaryColor.withOpacity(0.1)
|
||||
: Colors.transparent,
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
DateFormat('d').format(date),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 20,
|
||||
color:
|
||||
isSelectedDay ? ColorsManager.blue1 : ColorsManager.blackColor,
|
||||
Text(
|
||||
DateFormat('EEE').format(date).toUpperCase(),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 14,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
Text(
|
||||
DateFormat('d').format(date),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 30,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:calendar_view/calendar_view.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/event_tile_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/hatched_column_background.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/time_line_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/week_day_header.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
@ -23,6 +22,12 @@ class WeeklyCalendarPage extends StatelessWidget {
|
||||
this.endTime,
|
||||
this.selectedDateFromSideBarCalender,
|
||||
});
|
||||
static const double timeLineWidth = 65;
|
||||
static const int totalDays = 7;
|
||||
static const double dayColumnWidth = 220;
|
||||
|
||||
final double calendarContentWidth =
|
||||
timeLineWidth + (totalDays * dayColumnWidth);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -52,154 +57,152 @@ class WeeklyCalendarPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
final weekDays = _getWeekDays(weekStart);
|
||||
const double timeLineWidth = 90;
|
||||
|
||||
final selectedDayIndex =
|
||||
weekDays.indexWhere((d) => isSameDay(d, selectedDate));
|
||||
final selectedSidebarIndex = selectedDateFromSideBarCalender == null
|
||||
? -1
|
||||
: weekDays
|
||||
.indexWhere((d) => isSameDay(d, selectedDateFromSideBarCalender!));
|
||||
|
||||
const double timeLineWidth = 80;
|
||||
const int totalDays = 7;
|
||||
final DateTime highlightStart = DateTime(2025, 7, 10);
|
||||
final DateTime highlightEnd = DateTime(2025, 7, 19);
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final double calendarWidth = constraints.maxWidth;
|
||||
final double dayColumnWidth =
|
||||
(calendarWidth - timeLineWidth) / totalDays - 0.1;
|
||||
bool isInRange(DateTime date, DateTime start, DateTime end) {
|
||||
return !date.isBefore(start) && !date.isAfter(end);
|
||||
!date.isBefore(start) && !date.isAfter(end);
|
||||
// remove this line and Check if the date is within the range
|
||||
return false;
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 25.0, right: 25.0, top: 25),
|
||||
child: Stack(
|
||||
children: [
|
||||
WeekView(
|
||||
weekDetectorBuilder: ({
|
||||
required date,
|
||||
required height,
|
||||
required heightPerMinute,
|
||||
required minuteSlotSize,
|
||||
required width,
|
||||
}) {
|
||||
return isInRange(date, highlightStart, highlightEnd)
|
||||
? HatchedColumnBackground(
|
||||
backgroundColor: ColorsManager.grey800,
|
||||
lineColor: ColorsManager.textGray,
|
||||
opacity: 0.3,
|
||||
stripeSpacing: 12,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
)
|
||||
: const SizedBox();
|
||||
},
|
||||
pageViewPhysics: const NeverScrollableScrollPhysics(),
|
||||
key: ValueKey(weekStart),
|
||||
controller: eventController,
|
||||
initialDay: weekStart,
|
||||
startHour: startHour - 1,
|
||||
endHour: endHour,
|
||||
heightPerMinute: 1.1,
|
||||
showLiveTimeLineInAllDays: false,
|
||||
showVerticalLines: true,
|
||||
emulateVerticalOffsetBy: -80,
|
||||
startDay: WeekDays.monday,
|
||||
liveTimeIndicatorSettings: const LiveTimeIndicatorSettings(
|
||||
showBullet: false,
|
||||
height: 0,
|
||||
),
|
||||
weekDayBuilder: (date) {
|
||||
return WeekDayHeader(
|
||||
date: date,
|
||||
isSelectedDay: isSameDay(date, selectedDate),
|
||||
);
|
||||
},
|
||||
timeLineBuilder: (date) {
|
||||
return TimeLineWidget(date: date);
|
||||
},
|
||||
timeLineWidth: timeLineWidth,
|
||||
weekPageHeaderBuilder: (start, end) => Container(),
|
||||
weekTitleHeight: 60,
|
||||
weekNumberBuilder: (firstDayOfWeek) => Padding(
|
||||
padding: const EdgeInsets.only(right: 15, bottom: 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
firstDayOfWeek.timeZoneName.replaceAll(':00', ''),
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontSize: 12,
|
||||
color: ColorsManager.blackColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: SizedBox(
|
||||
width: calendarContentWidth,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 25.0, right: 25.0, top: 25),
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
child: WeekView(
|
||||
minuteSlotSize: MinuteSlotSize.minutes15,
|
||||
weekDetectorBuilder: ({
|
||||
required date,
|
||||
required height,
|
||||
required heightPerMinute,
|
||||
required minuteSlotSize,
|
||||
required width,
|
||||
}) {
|
||||
final isSelected = isSameDay(date, selectedDate);
|
||||
final isSidebarSelected =
|
||||
selectedDateFromSideBarCalender != null &&
|
||||
isSameDay(
|
||||
date, selectedDateFromSideBarCalender!);
|
||||
if (isSidebarSelected && !isSelected) {
|
||||
return Container(
|
||||
height: height,
|
||||
width: width,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange.withOpacity(0.13),
|
||||
),
|
||||
);
|
||||
} else if (isSelected) {
|
||||
return Container(
|
||||
height: height,
|
||||
width: width,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
ColorsManager.spaceColor.withOpacity(0.07),
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
|
||||
// weekDetectorBuilder: ({
|
||||
// required date,
|
||||
// required height,
|
||||
// required heightPerMinute,
|
||||
// required minuteSlotSize,
|
||||
// required width,
|
||||
// }) {
|
||||
// return isInRange(date, highlightStart, highlightEnd)
|
||||
// ? HatchedColumnBackground(
|
||||
// backgroundColor: ColorsManager.grey800,
|
||||
// lineColor: ColorsManager.textGray,
|
||||
// opacity: 0.3,
|
||||
// stripeSpacing: 12,
|
||||
// borderRadius: BorderRadius.circular(8),
|
||||
// )
|
||||
// : const SizedBox();
|
||||
// },
|
||||
pageViewPhysics: const NeverScrollableScrollPhysics(),
|
||||
key: ValueKey(weekStart),
|
||||
controller: eventController,
|
||||
initialDay: weekStart,
|
||||
startHour: startHour - 1,
|
||||
endHour: endHour,
|
||||
heightPerMinute: 1.7,
|
||||
showLiveTimeLineInAllDays: false,
|
||||
showVerticalLines: true,
|
||||
emulateVerticalOffsetBy: -95,
|
||||
startDay: WeekDays.monday,
|
||||
liveTimeIndicatorSettings:
|
||||
const LiveTimeIndicatorSettings(
|
||||
showBullet: false,
|
||||
height: 0,
|
||||
),
|
||||
weekDayBuilder: (date) {
|
||||
return WeekDayHeader(
|
||||
date: date,
|
||||
isSelectedDay: isSameDay(date, selectedDate),
|
||||
);
|
||||
},
|
||||
timeLineBuilder: (date) {
|
||||
return TimeLineWidget(date: date);
|
||||
},
|
||||
timeLineWidth: timeLineWidth,
|
||||
weekPageHeaderBuilder: (start, end) => Container(),
|
||||
weekTitleHeight: 90,
|
||||
weekNumberBuilder: (firstDayOfWeek) => Padding(
|
||||
padding: const EdgeInsets.only(right: 15, bottom: 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
firstDayOfWeek.timeZoneName
|
||||
.replaceAll(':00', ''),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(
|
||||
fontSize: 12,
|
||||
color: ColorsManager.blackColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
eventTileBuilder: (date, events, boundary, start, end) {
|
||||
return EventTileWidget(
|
||||
events: events,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
eventTileBuilder: (date, events, boundary, start, end) {
|
||||
return EventTileWidget(
|
||||
events: events,
|
||||
);
|
||||
},
|
||||
),
|
||||
if (selectedDayIndex >= 0)
|
||||
Positioned(
|
||||
left: (timeLineWidth + 3) +
|
||||
(dayColumnWidth - 8) * (selectedDayIndex - 0.01),
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: dayColumnWidth,
|
||||
child: IgnorePointer(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 0, horizontal: 4),
|
||||
color: ColorsManager.spaceColor.withOpacity(0.07),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (selectedSidebarIndex >= 0 &&
|
||||
selectedSidebarIndex != selectedDayIndex)
|
||||
Positioned(
|
||||
left: (timeLineWidth + 3) +
|
||||
(dayColumnWidth - 8) * (selectedSidebarIndex - 0.01),
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: dayColumnWidth,
|
||||
child: IgnorePointer(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 0, horizontal: 4),
|
||||
color: Colors.orange.withOpacity(0.14),
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 50,
|
||||
bottom: 0,
|
||||
child: IgnorePointer(
|
||||
child: Container(
|
||||
width: 1,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 50,
|
||||
bottom: 0,
|
||||
child: IgnorePointer(
|
||||
child: Container(
|
||||
width: 1,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<DateTime> _getWeekDays(DateTime date) {
|
||||
final int weekday = date.weekday;
|
||||
final DateTime monday = date.subtract(Duration(days: weekday - 1));
|
||||
return List.generate(7, (i) => monday.add(Duration(days: i)));
|
||||
}
|
||||
}
|
||||
|
||||
bool isSameDay(DateTime d1, DateTime d2) {
|
||||
|
@ -12,48 +12,46 @@ class BookingPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
color: Colors.blueGrey[100],
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'Side bar',
|
||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
||||
),
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
color: Colors.blueGrey[100],
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'Side bar',
|
||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
||||
),
|
||||
)),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: SizedBox(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
SvgTextButton(
|
||||
svgAsset: Assets.homeIcon,
|
||||
label: 'Manage Bookable Spaces',
|
||||
onPressed: () {
|
||||
pageController.jumpToPage(2);
|
||||
}),
|
||||
const SizedBox(width: 20),
|
||||
SvgTextButton(
|
||||
svgAsset: Assets.groupIcon,
|
||||
label: 'Manage Users',
|
||||
onPressed: () {})
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: SizedBox(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
SvgTextButton(
|
||||
svgAsset: Assets.homeIcon,
|
||||
label: 'Manage Bookable Spaces',
|
||||
onPressed: () {
|
||||
pageController.jumpToPage(2);
|
||||
}),
|
||||
const SizedBox(width: 20),
|
||||
SvgTextButton(
|
||||
svgAsset: Assets.groupIcon,
|
||||
label: 'Manage Users',
|
||||
onPressed: () {})
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
),
|
||||
))
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
import 'dart:async';
|
||||
|
||||
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/service/non_bookable_spaces_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
|
||||
class NonBookableSpacesDebouncerDecoratorService
|
||||
implements NonBookableSpacesService {
|
||||
final NonBookableSpacesService _delegate;
|
||||
Timer? _debounce;
|
||||
|
||||
NonBookableSpacesDebouncerDecoratorService(this._delegate);
|
||||
|
||||
@override
|
||||
Future<PaginatedDataModel<BookableSpacemodel>> load(
|
||||
NonBookableSpacesParams params) {
|
||||
final completer = Completer<PaginatedDataModel<BookableSpacemodel>>();
|
||||
|
||||
_debounce?.cancel();
|
||||
_debounce = Timer(const Duration(milliseconds: 500), () async {
|
||||
try {
|
||||
final result = await _delegate.load(params);
|
||||
completer.complete(result);
|
||||
} catch (e) {
|
||||
completer.completeError(e);
|
||||
}
|
||||
});
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ class RemoteBookableSpacesService implements BookableSpacesService {
|
||||
static const _defaultErrorMessage = 'Failed to load Bookable Spaces';
|
||||
@override
|
||||
Future<PaginatedDataModel<BookableSpacemodel>> load(
|
||||
BookableSpacesParams param) async {
|
||||
BookableSpacesParam param) async {
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
path: ApiEndpoints.bookableSpaces,
|
||||
|
@ -3,7 +3,6 @@ import 'dart:async';
|
||||
import 'package:dio/dio.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/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
@ -11,63 +10,32 @@ import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
class RemoteNonBookableSpaces implements NonBookableSpacesService {
|
||||
Timer? _debounce;
|
||||
|
||||
final HTTPService _httpService;
|
||||
|
||||
RemoteNonBookableSpaces(this._httpService);
|
||||
|
||||
static const _defaultErrorMessage = 'Failed to load Spaces';
|
||||
|
||||
@override
|
||||
Future<PaginatedDataModel<BookableSpacemodel>> load(
|
||||
NonBookableSpacesParams params) {
|
||||
final completer = Completer<PaginatedDataModel<BookableSpacemodel>>();
|
||||
|
||||
_debounce?.cancel();
|
||||
_debounce = Timer(const Duration(milliseconds: 500), () async {
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
path: ApiEndpoints.bookableSpaces,
|
||||
queryParameters: {
|
||||
'configured': false,
|
||||
'page': params.currentPage,
|
||||
'search': params.searchedWords,
|
||||
},
|
||||
expectedResponseModel: (json) {
|
||||
final result = json as Map<String, dynamic>;
|
||||
return PaginatedDataModel.fromJson(
|
||||
result,
|
||||
BookableSpacemodel.fromJsonList,
|
||||
);
|
||||
},
|
||||
);
|
||||
completer.complete(response);
|
||||
} on DioException catch (e) {
|
||||
final message = e.response?.data as Map<String, dynamic>?;
|
||||
final error = message?['error'] as Map<String, dynamic>?;
|
||||
final errorMessage = error?['error'] as String? ?? '';
|
||||
final formattedErrorMessage = [
|
||||
_defaultErrorMessage,
|
||||
errorMessage,
|
||||
].join(': ');
|
||||
completer.completeError(APIException(formattedErrorMessage));
|
||||
} catch (e) {
|
||||
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
|
||||
completer.completeError(APIException(formattedErrorMessage));
|
||||
}
|
||||
});
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> sendBookableSpacesToApi(
|
||||
SendBookableSpacesToApiParams params) async {
|
||||
NonBookableSpacesParams params) async {
|
||||
try {
|
||||
await _httpService.post(
|
||||
final response = await _httpService.get(
|
||||
path: ApiEndpoints.bookableSpaces,
|
||||
body: params.toJson(),
|
||||
expectedResponseModel: (p0) {},
|
||||
queryParameters: {
|
||||
'configured': false,
|
||||
'page': params.currentPage,
|
||||
'search': params.searchedWords,
|
||||
},
|
||||
expectedResponseModel: (json) {
|
||||
final result = json as Map<String, dynamic>;
|
||||
return PaginatedDataModel.fromJson(
|
||||
result,
|
||||
BookableSpacemodel.fromJsonList,
|
||||
);
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} on DioException catch (e) {
|
||||
final message = e.response?.data as Map<String, dynamic>?;
|
||||
final error = message?['error'] as Map<String, dynamic>?;
|
||||
|
@ -0,0 +1,35 @@
|
||||
import 'package:dio/dio.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/send_bookable_spaces_service.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
class RemoteSendBookableSpaces implements SendBookableSpacesService {
|
||||
final HTTPService _httpService;
|
||||
RemoteSendBookableSpaces(this._httpService);
|
||||
static const _defaultErrorMessage = 'Failed to load Spaces';
|
||||
@override
|
||||
Future<void> sendBookableSpacesToApi(
|
||||
SendBookableSpacesToApiParams params) async {
|
||||
try {
|
||||
await _httpService.post(
|
||||
path: ApiEndpoints.bookableSpaces,
|
||||
body: params.toJson(),
|
||||
expectedResponseModel: (p0) {},
|
||||
);
|
||||
} on DioException catch (e) {
|
||||
final message = e.response?.data as Map<String, dynamic>?;
|
||||
final error = message?['error'] as Map<String, dynamic>?;
|
||||
final errorMessage = error?['error'] as String? ?? '';
|
||||
final formattedErrorMessage = [
|
||||
_defaultErrorMessage,
|
||||
errorMessage,
|
||||
].join(': ');
|
||||
throw APIException(formattedErrorMessage);
|
||||
} catch (e) {
|
||||
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
|
||||
throw APIException(formattedErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BookableSpaceConfig {
|
||||
String configUuid;
|
||||
List<String> bookableDays;
|
||||
TimeOfDay? bookingStartTime;
|
||||
TimeOfDay? bookingEndTime;
|
||||
int cost;
|
||||
bool availability;
|
||||
final String configUuid;
|
||||
final List<String> bookableDays;
|
||||
final TimeOfDay? bookingStartTime;
|
||||
final TimeOfDay? bookingEndTime;
|
||||
final int cost;
|
||||
final bool availability;
|
||||
BookableSpaceConfig({
|
||||
required this.configUuid,
|
||||
required this.availability,
|
||||
|
@ -1,10 +1,10 @@
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_config.dart';
|
||||
|
||||
class BookableSpacemodel {
|
||||
String spaceUuid;
|
||||
String spaceName;
|
||||
BookableSpaceConfig? spaceConfig;
|
||||
String spaceVirtualAddress;
|
||||
final String spaceUuid;
|
||||
final String spaceName;
|
||||
final BookableSpaceConfig? spaceConfig;
|
||||
final String spaceVirtualAddress;
|
||||
|
||||
BookableSpacemodel({
|
||||
required this.spaceUuid,
|
||||
|
@ -1,6 +1,6 @@
|
||||
class BookableSpacesParams {
|
||||
int currentPage;
|
||||
BookableSpacesParams({
|
||||
class BookableSpacesParam {
|
||||
final int currentPage;
|
||||
BookableSpacesParam({
|
||||
required this.currentPage,
|
||||
});
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
class NonBookableSpacesParams {
|
||||
int currentPage;
|
||||
String? searchedWords;
|
||||
final int currentPage;
|
||||
final String? searchedWords;
|
||||
NonBookableSpacesParams({
|
||||
required this.currentPage,
|
||||
this.searchedWords,
|
||||
|
@ -2,11 +2,11 @@ import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domai
|
||||
import 'package:syncrow_web/utils/string_utils.dart';
|
||||
|
||||
class SendBookableSpacesToApiParams {
|
||||
List<String> spaceUuids;
|
||||
List<String> daysAvailable;
|
||||
String startTime;
|
||||
String endTime;
|
||||
int points;
|
||||
final List<String> spaceUuids;
|
||||
final List<String> daysAvailable;
|
||||
final String startTime;
|
||||
final String endTime;
|
||||
final int points;
|
||||
SendBookableSpacesToApiParams({
|
||||
required this.spaceUuids,
|
||||
required this.daysAvailable,
|
||||
|
@ -1,15 +1,13 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/utils/string_utils.dart';
|
||||
|
||||
class UpdateBookableSpaceParam {
|
||||
String spaceUuid;
|
||||
|
||||
List<String>? bookableDays;
|
||||
String? bookingStartTime;
|
||||
String? bookingEndTime;
|
||||
int? cost;
|
||||
bool? availability;
|
||||
final String spaceUuid;
|
||||
final List<String>? bookableDays;
|
||||
final String? bookingStartTime;
|
||||
final String? bookingEndTime;
|
||||
final int? cost;
|
||||
final bool? availability;
|
||||
UpdateBookableSpaceParam({
|
||||
required this.spaceUuid,
|
||||
this.bookingStartTime,
|
||||
|
@ -4,5 +4,5 @@ import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/
|
||||
|
||||
abstract class BookableSpacesService {
|
||||
Future<PaginatedDataModel<BookableSpacemodel>> load(
|
||||
BookableSpacesParams param);
|
||||
BookableSpacesParam param);
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
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/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
|
||||
abstract class NonBookableSpacesService {
|
||||
Future<PaginatedDataModel<BookableSpacemodel>> load(
|
||||
NonBookableSpacesParams params);
|
||||
Future<void> sendBookableSpacesToApi(SendBookableSpacesToApiParams params);
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/send_bookable_spaces_to_api_params.dart';
|
||||
|
||||
abstract class SendBookableSpacesService{
|
||||
Future<void> sendBookableSpacesToApi(SendBookableSpacesToApiParams params);
|
||||
}
|
@ -40,22 +40,25 @@ class BookableSpacesBloc
|
||||
|
||||
if (event.bookableSpace.spaceConfig!.configUuid ==
|
||||
event.updatedBookableSpaceConfig.configUuid) {
|
||||
final editedBookableSpace = event.bookableSpaces.data.firstWhere(
|
||||
(element) => element.spaceUuid == event.bookableSpace.spaceUuid,
|
||||
);
|
||||
final config = editedBookableSpace.spaceConfig!.copyWith(
|
||||
availability: event.updatedBookableSpaceConfig.availability,
|
||||
bookableDays: event.updatedBookableSpaceConfig.bookableDays,
|
||||
bookingEndTime: event.updatedBookableSpaceConfig.bookingEndTime,
|
||||
bookingStartTime: event.updatedBookableSpaceConfig.bookingStartTime,
|
||||
cost: event.updatedBookableSpaceConfig.cost,
|
||||
);
|
||||
editedBookableSpace.spaceConfig = config;
|
||||
final index = event.bookableSpaces.data.indexWhere(
|
||||
(element) => element.spaceUuid == event.bookableSpace.spaceUuid,
|
||||
);
|
||||
event.bookableSpaces.data.removeAt(index);
|
||||
event.bookableSpaces.data.insert(index, editedBookableSpace);
|
||||
|
||||
if (index != -1) {
|
||||
final original = event.bookableSpaces.data[index];
|
||||
|
||||
final updatedConfig = original.spaceConfig!.copyWith(
|
||||
availability: event.updatedBookableSpaceConfig.availability,
|
||||
bookableDays: event.updatedBookableSpaceConfig.bookableDays,
|
||||
bookingEndTime: event.updatedBookableSpaceConfig.bookingEndTime,
|
||||
bookingStartTime: event.updatedBookableSpaceConfig.bookingStartTime,
|
||||
cost: event.updatedBookableSpaceConfig.cost,
|
||||
);
|
||||
|
||||
final updatedSpace = original.copyWith(spaceConfig: updatedConfig);
|
||||
|
||||
event.bookableSpaces.data[index] = updatedSpace;
|
||||
}
|
||||
}
|
||||
|
||||
emit(BookableSpacesLoaded(bookableSpacesList: event.bookableSpaces));
|
||||
|
@ -8,7 +8,7 @@ sealed class BookableSpacesEvent extends Equatable {
|
||||
}
|
||||
|
||||
class LoadBookableSpacesEvent extends BookableSpacesEvent {
|
||||
final BookableSpacesParams param;
|
||||
final BookableSpacesParam param;
|
||||
const LoadBookableSpacesEvent(this.param);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,33 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.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/send_bookable_spaces_to_api_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/send_bookable_spaces_service.dart';
|
||||
|
||||
part 'send_bookable_spaces_event.dart';
|
||||
part 'send_bookable_spaces_state.dart';
|
||||
|
||||
class SendBookableSpacesBloc
|
||||
extends Bloc<SendBookableSpacesEvent, SendBookableSpacesState> {
|
||||
SendBookableSpacesService sendBookableSpacesService;
|
||||
SendBookableSpacesBloc(
|
||||
this.sendBookableSpacesService,
|
||||
) : super(SendBookableSpacesInitial()) {
|
||||
on<SendBookableSpacesToApi>(_onSendBookableSpacesToApi);
|
||||
}
|
||||
Future<void> _onSendBookableSpacesToApi(SendBookableSpacesToApi event,
|
||||
Emitter<SendBookableSpacesState> emit) async {
|
||||
emit(SendBookableSpacesLoading());
|
||||
try {
|
||||
await sendBookableSpacesService.sendBookableSpacesToApi(
|
||||
SendBookableSpacesToApiParams.fromBookableSpacesModel(
|
||||
event.selectedBookableSpaces),
|
||||
);
|
||||
emit(SendBookableSpacesSuccess());
|
||||
} catch (e) {
|
||||
emit(
|
||||
SendBookableSpacesError(e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
part of 'send_bookable_spaces_bloc.dart';
|
||||
|
||||
sealed class SendBookableSpacesEvent extends Equatable {
|
||||
const SendBookableSpacesEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class SendBookableSpacesToApi extends SendBookableSpacesEvent {
|
||||
final List<BookableSpacemodel> selectedBookableSpaces;
|
||||
const SendBookableSpacesToApi({required this.selectedBookableSpaces});
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
part of 'send_bookable_spaces_bloc.dart';
|
||||
|
||||
sealed class SendBookableSpacesState extends Equatable {
|
||||
const SendBookableSpacesState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class SendBookableSpacesInitial extends SendBookableSpacesState {}
|
||||
|
||||
class SendBookableSpacesLoading extends SendBookableSpacesState {}
|
||||
|
||||
class SendBookableSpacesSuccess extends SendBookableSpacesState {}
|
||||
|
||||
class SendBookableSpacesError extends SendBookableSpacesState {
|
||||
final String error;
|
||||
const SendBookableSpacesError(this.error);
|
||||
}
|
@ -2,7 +2,6 @@ import 'package:bloc/bloc.dart';
|
||||
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/send_bookable_spaces_to_api_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/non_bookable_spaces_service.dart';
|
||||
|
||||
part 'setup_bookable_spaces_event.dart';
|
||||
@ -11,68 +10,123 @@ part 'setup_bookable_spaces_state.dart';
|
||||
class SetupBookableSpacesBloc
|
||||
extends Bloc<SetupBookableSpacesEvent, SetupBookableSpacesState> {
|
||||
NonBookableSpacesService nonBookableSpacesService;
|
||||
List<BookableSpacemodel> selectedBookableSpaces = [];
|
||||
|
||||
SetupBookableSpacesBloc(this.nonBookableSpacesService)
|
||||
: super(SetupBookableSpacesInitial()) {
|
||||
: super(const SetupBookableSpacesInitial(bookableSpaces: [])) {
|
||||
on<AddToBookableSpaceEvent>(_onAddToBookableSpaceEvent);
|
||||
on<RemoveFromBookableSpaceEvent>(_onRemoveFromBookableSpaceEvent);
|
||||
on<SendBookableSpacesToApi>(_onSendBookableSpacesToApi);
|
||||
on<AddBookableDaysEvent>(_onAddBookableDays);
|
||||
on<ChangeStartTimeEvent>(_onChangeStartTimeEvent);
|
||||
on<ChangeEndTimeEvent>(_onChangeEndTimeEvent);
|
||||
on<ChangeCostEvent>(_onChangeCostEvent);
|
||||
|
||||
on<CheckConfigurValidityEvent>(_onCheckConfigurValidityEvent);
|
||||
on<EditModeSelected>(_onEditModeSelected);
|
||||
}
|
||||
TimeOfDay? get endTime =>
|
||||
selectedBookableSpaces.first.spaceConfig!.bookingEndTime;
|
||||
|
||||
TimeOfDay? get startTime =>
|
||||
selectedBookableSpaces.first.spaceConfig!.bookingStartTime;
|
||||
List<BookableSpacemodel> get currentBookableSpaces {
|
||||
return switch (state) {
|
||||
AddNonBookableSpaceIntoBookableState(:final bookableSpaces) =>
|
||||
bookableSpaces,
|
||||
RemoveBookableSpaceIntoNonBookableState(:final bookableSpaces) =>
|
||||
bookableSpaces,
|
||||
SetupBookableSpacesInitial(:final bookableSpaces) => bookableSpaces,
|
||||
InProgressState(:final bookableSpaces) => bookableSpaces,
|
||||
ValidSaveButtonState(:final bookableSpaces) => bookableSpaces,
|
||||
UnValidSaveButtonState(:final bookableSpaces) => bookableSpaces,
|
||||
};
|
||||
}
|
||||
|
||||
void _onAddToBookableSpaceEvent(
|
||||
AddToBookableSpaceEvent event,
|
||||
Emitter<SetupBookableSpacesState> emit,
|
||||
) {
|
||||
emit(InProgressState());
|
||||
selectedBookableSpaces.add(event.nonBookableSpace);
|
||||
emit(AddNonBookableSpaceIntoBookableState(
|
||||
bookableSpaces: selectedBookableSpaces));
|
||||
emit(InProgressState(bookableSpaces: state.bookableSpaces));
|
||||
final updatedSpaces = List<BookableSpacemodel>.from(state.bookableSpaces);
|
||||
|
||||
updatedSpaces.add(event.nonBookableSpace);
|
||||
|
||||
emit(AddNonBookableSpaceIntoBookableState(bookableSpaces: updatedSpaces));
|
||||
}
|
||||
|
||||
void _onRemoveFromBookableSpaceEvent(RemoveFromBookableSpaceEvent event,
|
||||
Emitter<SetupBookableSpacesState> emit) {
|
||||
emit(InProgressState());
|
||||
selectedBookableSpaces.remove(event.bookableSpace);
|
||||
emit(InProgressState(bookableSpaces: state.bookableSpaces));
|
||||
state.bookableSpaces.remove(event.bookableSpace);
|
||||
emit(RemoveBookableSpaceIntoNonBookableState(
|
||||
bookableSpaces: selectedBookableSpaces));
|
||||
bookableSpaces: state.bookableSpaces));
|
||||
}
|
||||
|
||||
Future<void> _onSendBookableSpacesToApi(SendBookableSpacesToApi event,
|
||||
Emitter<SetupBookableSpacesState> emit) async {
|
||||
emit(SendBookableSpacesLoading());
|
||||
try {
|
||||
await nonBookableSpacesService.sendBookableSpacesToApi(
|
||||
SendBookableSpacesToApiParams.fromBookableSpacesModel(
|
||||
selectedBookableSpaces,
|
||||
),
|
||||
void _onAddBookableDays(
|
||||
AddBookableDaysEvent event, Emitter<SetupBookableSpacesState> emit) {
|
||||
final updatedSpaces = state.bookableSpaces.map((space) {
|
||||
final updatedConfig = space.spaceConfig?.copyWith(
|
||||
bookableDays: event.bookableDays,
|
||||
);
|
||||
emit(SendBookableSpacesSuccess());
|
||||
} catch (e) {
|
||||
emit(
|
||||
SendBookableSpacesError(e.toString()),
|
||||
|
||||
return space.copyWith(spaceConfig: updatedConfig);
|
||||
}).toList();
|
||||
|
||||
emit(SetupBookableSpacesInitial(bookableSpaces: updatedSpaces));
|
||||
}
|
||||
|
||||
void _onChangeStartTimeEvent(
|
||||
ChangeStartTimeEvent event, Emitter<SetupBookableSpacesState> emit) {
|
||||
final updatedSpaces = state.bookableSpaces.map((space) {
|
||||
final updatedConfig = space.spaceConfig?.copyWith(
|
||||
bookingStartTime: event.startTime,
|
||||
);
|
||||
}
|
||||
|
||||
return space.copyWith(spaceConfig: updatedConfig);
|
||||
}).toList();
|
||||
|
||||
emit(SetupBookableSpacesInitial(bookableSpaces: updatedSpaces));
|
||||
}
|
||||
|
||||
void _onChangeEndTimeEvent(
|
||||
ChangeEndTimeEvent event, Emitter<SetupBookableSpacesState> emit) {
|
||||
final updatedSpaces = state.bookableSpaces.map((space) {
|
||||
final updatedConfig = space.spaceConfig?.copyWith(
|
||||
bookingEndTime: event.endTime,
|
||||
);
|
||||
|
||||
return space.copyWith(spaceConfig: updatedConfig);
|
||||
}).toList();
|
||||
|
||||
emit(SetupBookableSpacesInitial(bookableSpaces: updatedSpaces));
|
||||
}
|
||||
|
||||
void _onChangeCostEvent(
|
||||
ChangeCostEvent event, Emitter<SetupBookableSpacesState> emit) {
|
||||
final updatedSpaces = state.bookableSpaces.map((space) {
|
||||
final updatedConfig = space.spaceConfig?.copyWith(
|
||||
cost: event.cost,
|
||||
);
|
||||
|
||||
return space.copyWith(spaceConfig: updatedConfig);
|
||||
}).toList();
|
||||
|
||||
emit(SetupBookableSpacesInitial(bookableSpaces: updatedSpaces));
|
||||
}
|
||||
|
||||
void _onCheckConfigurValidityEvent(CheckConfigurValidityEvent event,
|
||||
Emitter<SetupBookableSpacesState> emit) {
|
||||
if (selectedBookableSpaces.first.spaceConfig!.isValid) {
|
||||
emit(ValidSaveButtonState());
|
||||
if (state.bookableSpaces.first.spaceConfig!.isValid) {
|
||||
emit(ValidSaveButtonState(
|
||||
bookableSpaces: state.bookableSpaces,
|
||||
));
|
||||
} else {
|
||||
emit(UnValidSaveButtonState());
|
||||
emit(UnValidSaveButtonState(
|
||||
bookableSpaces: state.bookableSpaces,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void _onEditModeSelected(
|
||||
EditModeSelected event, Emitter<SetupBookableSpacesState> emit) {
|
||||
selectedBookableSpaces.clear();
|
||||
selectedBookableSpaces.add(event.editingBookableSpace);
|
||||
EditModeSelected event,
|
||||
Emitter<SetupBookableSpacesState> emit,
|
||||
) {
|
||||
final updatedList = [event.editingBookableSpace];
|
||||
|
||||
emit(SetupBookableSpacesInitial(bookableSpaces: updatedList));
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ sealed class SetupBookableSpacesEvent extends Equatable {
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class AddToBookableSpaceEvent extends SetupBookableSpacesEvent {
|
||||
final BookableSpacemodel nonBookableSpace;
|
||||
const AddToBookableSpaceEvent({
|
||||
@ -20,7 +21,33 @@ class RemoveFromBookableSpaceEvent extends SetupBookableSpacesEvent {
|
||||
});
|
||||
}
|
||||
|
||||
class SendBookableSpacesToApi extends SetupBookableSpacesEvent {}
|
||||
class AddBookableDaysEvent extends SetupBookableSpacesEvent {
|
||||
final List<String> bookableDays;
|
||||
const AddBookableDaysEvent({
|
||||
required this.bookableDays,
|
||||
});
|
||||
}
|
||||
|
||||
class ChangeCostEvent extends SetupBookableSpacesEvent {
|
||||
final int cost;
|
||||
const ChangeCostEvent({
|
||||
required this.cost,
|
||||
});
|
||||
}
|
||||
|
||||
class ChangeStartTimeEvent extends SetupBookableSpacesEvent {
|
||||
final TimeOfDay startTime;
|
||||
const ChangeStartTimeEvent({
|
||||
required this.startTime,
|
||||
});
|
||||
}
|
||||
|
||||
class ChangeEndTimeEvent extends SetupBookableSpacesEvent {
|
||||
final TimeOfDay endTime;
|
||||
const ChangeEndTimeEvent({
|
||||
required this.endTime,
|
||||
});
|
||||
}
|
||||
|
||||
class CheckConfigurValidityEvent extends SetupBookableSpacesEvent {}
|
||||
|
||||
@ -29,4 +56,4 @@ class EditModeSelected extends SetupBookableSpacesEvent {
|
||||
const EditModeSelected({
|
||||
required this.editingBookableSpace,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,39 +1,37 @@
|
||||
part of 'setup_bookable_spaces_bloc.dart';
|
||||
|
||||
sealed class SetupBookableSpacesState extends Equatable {
|
||||
const SetupBookableSpacesState();
|
||||
|
||||
final List<BookableSpacemodel> bookableSpaces;
|
||||
const SetupBookableSpacesState({required this.bookableSpaces});
|
||||
TimeOfDay? get startTime =>
|
||||
bookableSpaces.first.spaceConfig!.bookingStartTime;
|
||||
TimeOfDay? get endTime => bookableSpaces.first.spaceConfig!.bookingEndTime;
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class SetupBookableSpacesInitial extends SetupBookableSpacesState {}
|
||||
final class SetupBookableSpacesInitial extends SetupBookableSpacesState {
|
||||
const SetupBookableSpacesInitial({required super.bookableSpaces});
|
||||
}
|
||||
|
||||
class AddNonBookableSpaceIntoBookableState extends SetupBookableSpacesState {
|
||||
final List<BookableSpacemodel> bookableSpaces;
|
||||
const AddNonBookableSpaceIntoBookableState({
|
||||
required this.bookableSpaces,
|
||||
});
|
||||
const AddNonBookableSpaceIntoBookableState({required super.bookableSpaces});
|
||||
}
|
||||
|
||||
class InProgressState extends SetupBookableSpacesState {}
|
||||
class InProgressState extends SetupBookableSpacesState {
|
||||
const InProgressState({required super.bookableSpaces});
|
||||
}
|
||||
|
||||
class RemoveBookableSpaceIntoNonBookableState extends SetupBookableSpacesState {
|
||||
final List<BookableSpacemodel> bookableSpaces;
|
||||
const RemoveBookableSpaceIntoNonBookableState({
|
||||
required this.bookableSpaces,
|
||||
required super.bookableSpaces,
|
||||
});
|
||||
}
|
||||
|
||||
class ValidSaveButtonState extends SetupBookableSpacesState {}
|
||||
|
||||
class UnValidSaveButtonState extends SetupBookableSpacesState {}
|
||||
|
||||
class SendBookableSpacesLoading extends SetupBookableSpacesState {}
|
||||
|
||||
class SendBookableSpacesSuccess extends SetupBookableSpacesState {}
|
||||
|
||||
class SendBookableSpacesError extends SetupBookableSpacesState {
|
||||
final String error;
|
||||
const SendBookableSpacesError(this.error);
|
||||
class ValidSaveButtonState extends SetupBookableSpacesState {
|
||||
const ValidSaveButtonState({required super.bookableSpaces});
|
||||
}
|
||||
|
||||
class UnValidSaveButtonState extends SetupBookableSpacesState {
|
||||
const UnValidSaveButtonState({required super.bookableSpaces});
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import 'package:equatable/equatable.dart';
|
||||
part 'steps_state.dart';
|
||||
|
||||
class StepsCubit extends Cubit<StepsState> {
|
||||
StepsCubit() : super(StepsInitial());
|
||||
StepsCubit() : super(StepOneState());
|
||||
|
||||
void initDialogValue() {
|
||||
emit(StepOneState());
|
||||
|
@ -7,9 +7,6 @@ sealed class StepsState extends Equatable {
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class StepsInitial extends StepsState {}
|
||||
|
||||
final class StepOneState extends StepsState {}
|
||||
|
||||
final class StepTwoState extends StepsState {}
|
||||
|
||||
|
@ -4,15 +4,13 @@ import 'package:equatable/equatable.dart';
|
||||
part 'toggle_points_switch_state.dart';
|
||||
|
||||
class TogglePointsSwitchCubit extends Cubit<TogglePointsSwitchState> {
|
||||
TogglePointsSwitchCubit() : super(TogglePointsSwitchInitial());
|
||||
bool switchValue = true;
|
||||
TogglePointsSwitchCubit() : super(UnActivatePointsSwitch());
|
||||
|
||||
void activateSwitch() {
|
||||
switchValue = true;
|
||||
emit(ActivatePointsSwitch());
|
||||
}
|
||||
|
||||
void unActivateSwitch() {
|
||||
switchValue = false;
|
||||
emit(UnActivatePointsSwitch());
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ sealed class TogglePointsSwitchState extends Equatable {
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class TogglePointsSwitchInitial extends TogglePointsSwitchState {}
|
||||
|
||||
class ActivatePointsSwitch extends TogglePointsSwitchState {}
|
||||
|
||||
|
@ -18,10 +18,10 @@ class UpdateBookableSpacesBloc
|
||||
|
||||
Future<void> _onUpdateBookableSpace(UpdateBookableSpace event,
|
||||
Emitter<UpdateBookableSpacesState> emit) async {
|
||||
emit(UpdateBookableSpaceLoading(event.updatedParams.spaceUuid));
|
||||
emit(UpdateBookableSpaceLoading(event.updatedParam.spaceUuid));
|
||||
try {
|
||||
final updatedSpace =
|
||||
await updateBookableSpaceService.update(event.updatedParams);
|
||||
await updateBookableSpaceService.update(event.updatedParam);
|
||||
|
||||
emit(UpdateBookableSpaceSuccess(bookableSpaceConfig: updatedSpace));
|
||||
event.onSuccess?.call();
|
||||
|
@ -9,9 +9,9 @@ sealed class UpdateBookableSpaceEvent extends Equatable {
|
||||
|
||||
class UpdateBookableSpace extends UpdateBookableSpaceEvent {
|
||||
final void Function()? onSuccess;
|
||||
final UpdateBookableSpaceParam updatedParams;
|
||||
final UpdateBookableSpaceParam updatedParam;
|
||||
const UpdateBookableSpace({
|
||||
required this.updatedParams,
|
||||
required this.updatedParam,
|
||||
this.onSuccess,
|
||||
});
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ class _ManageBookableSpacesPageState extends State<ManageBookableSpacesPage> {
|
||||
RemoteBookableSpacesService(HTTPService()),
|
||||
)..add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: 1),
|
||||
BookableSpacesParam(currentPage: 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -1,9 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/data/non_bookable_spaces_decorator.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/remote_send_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/send_bookable_spaces_bloc/send_bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/setup_bookable_spaces_bloc/setup_bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/steps_cubit/cubit/steps_cubit.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/details_steps_widget.dart';
|
||||
@ -44,7 +47,9 @@ class _SetupBookableSpacesDialogState extends State<SetupBookableSpacesDialog> {
|
||||
),
|
||||
BlocProvider<NonBookableSpacesBloc>(
|
||||
create: (context) => NonBookableSpacesBloc(
|
||||
RemoteNonBookableSpaces(HTTPService()),
|
||||
NonBookableSpacesDebouncerDecoratorService(
|
||||
RemoteNonBookableSpaces(HTTPService()),
|
||||
),
|
||||
)..add(
|
||||
LoadUnBookableSpacesEvent(
|
||||
nonBookableSpacesParams:
|
||||
@ -58,9 +63,15 @@ class _SetupBookableSpacesDialogState extends State<SetupBookableSpacesDialog> {
|
||||
RemoteNonBookableSpaces(HTTPService()))
|
||||
: (context) => SetupBookableSpacesBloc(
|
||||
RemoteNonBookableSpaces(HTTPService()))
|
||||
..add(EditModeSelected(
|
||||
editingBookableSpace: widget.editingBookableSpace!,
|
||||
)),
|
||||
..add(
|
||||
EditModeSelected(
|
||||
editingBookableSpace: widget.editingBookableSpace!),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => SendBookableSpacesBloc(
|
||||
RemoteSendBookableSpaces(HTTPService()),
|
||||
),
|
||||
)
|
||||
],
|
||||
child: AlertDialog(
|
||||
@ -105,16 +116,14 @@ class _SetupBookableSpacesDialogState extends State<SetupBookableSpacesDialog> {
|
||||
),
|
||||
Builder(builder: (context) {
|
||||
final stepsState = context.watch<StepsCubit>().state;
|
||||
final setupBookableSpacesBloc =
|
||||
context.watch<SetupBookableSpacesBloc>();
|
||||
final selectedSpaces =
|
||||
setupBookableSpacesBloc.selectedBookableSpaces;
|
||||
final bookableSpaces =
|
||||
context.watch<SetupBookableSpacesBloc>().state.bookableSpaces;
|
||||
return stepsState is StepOneState
|
||||
? NextFirstStepButton(selectedSpaces: selectedSpaces)
|
||||
? const NextFirstStepButton()
|
||||
: SaveSecondStepButton(
|
||||
selectedSpaces: selectedSpaces,
|
||||
pointsController: pointsController,
|
||||
isEditingMode: widget.editingBookableSpace != null,
|
||||
bookableSpaces: bookableSpaces,
|
||||
);
|
||||
}),
|
||||
],
|
||||
|
@ -47,7 +47,7 @@ class BookableSpaceSwitchActivationWidget extends StatelessWidget {
|
||||
return ColorsManager.whiteColors;
|
||||
}),
|
||||
value: space.spaceConfig!.availability,
|
||||
activeTrackColor: ColorsManager.blueColor,
|
||||
activeTrackColor: ColorsManager.dialogBlueTitle,
|
||||
inactiveTrackColor: ColorsManager.grayBorder,
|
||||
thumbColor: WidgetStateProperty.resolveWith<Color>(
|
||||
(Set<WidgetState> states) {
|
||||
@ -56,7 +56,7 @@ class BookableSpaceSwitchActivationWidget extends StatelessWidget {
|
||||
onChanged: (value) {
|
||||
context.read<UpdateBookableSpacesBloc>().add(
|
||||
UpdateBookableSpace(
|
||||
updatedParams: UpdateBookableSpaceParam(
|
||||
updatedParam: UpdateBookableSpaceParam(
|
||||
spaceUuid: space.spaceUuid,
|
||||
availability: value,
|
||||
)),
|
||||
|
@ -17,6 +17,8 @@ class BookingPeriodWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final state = context.watch<SetupBookableSpacesBloc>().state;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -32,98 +34,108 @@ class BookingPeriodWidget extends StatelessWidget {
|
||||
const Text('Booking Period'),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Container(
|
||||
width: 300,
|
||||
width: 230,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: ColorsManager.graysColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: ColorsManager.circleRolesBackground,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
offset: Offset.zero,
|
||||
blurRadius: 4,
|
||||
spreadRadius: 0,
|
||||
color: ColorsManager.timePickerColor.withValues(alpha: 0.15),
|
||||
)
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
TimePickerWidget(
|
||||
title: editingBookableSpace == null
|
||||
? 'Start Time'
|
||||
: editingBookableSpace!.spaceConfig!.bookingStartTime!
|
||||
.format(context),
|
||||
onTimePicked: (timePicked) {
|
||||
if (timePicked == null) {
|
||||
title: editingBookableSpace?.spaceConfig?.bookingStartTime
|
||||
?.format(context) ??
|
||||
'Start Time',
|
||||
onTimePicked: (pickedStartTime) {
|
||||
if (pickedStartTime == null) return;
|
||||
|
||||
if (state.endTime != null &&
|
||||
isEndTimeAfterStartTime(
|
||||
pickedStartTime, state.endTime!)) {
|
||||
_showInvalidSnackBar(
|
||||
context, "You can't choose Start Time after End Time");
|
||||
return;
|
||||
}
|
||||
final setupBookableSpacesBloc =
|
||||
context.read<SetupBookableSpacesBloc>();
|
||||
|
||||
if (setupBookableSpacesBloc.endTime != null &&
|
||||
isEndTimeAfterStartTime(
|
||||
timePicked, setupBookableSpacesBloc.endTime!)) {
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||
content:
|
||||
Text("You can't choose start Time Before End time"),
|
||||
duration: Duration(seconds: 2),
|
||||
backgroundColor: ColorsManager.red,
|
||||
));
|
||||
throw Exception();
|
||||
} else {
|
||||
setupBookableSpacesBloc.selectedBookableSpaces.forEach(
|
||||
(e) => e.spaceConfig!.bookingStartTime = timePicked,
|
||||
);
|
||||
}
|
||||
context.read<SetupBookableSpacesBloc>().add(
|
||||
ChangeStartTimeEvent(startTime: pickedStartTime),
|
||||
);
|
||||
context.read<SetupBookableSpacesBloc>().add(
|
||||
CheckConfigurValidityEvent(),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
const Icon(
|
||||
Icons.arrow_right_alt,
|
||||
color: ColorsManager.grayColor,
|
||||
size: 13,
|
||||
),
|
||||
TimePickerWidget(
|
||||
title: editingBookableSpace == null
|
||||
? 'End Time'
|
||||
: editingBookableSpace!.spaceConfig!.bookingEndTime!
|
||||
.format(context),
|
||||
onTimePicked: (timePicked) {
|
||||
if (timePicked == null) {
|
||||
title: editingBookableSpace?.spaceConfig?.bookingEndTime
|
||||
?.format(context) ??
|
||||
'End Time',
|
||||
onTimePicked: (pickedEndTime) {
|
||||
if (pickedEndTime == null) return;
|
||||
|
||||
if (state.startTime != null &&
|
||||
isEndTimeAfterStartTime(
|
||||
state.startTime!, pickedEndTime)) {
|
||||
_showInvalidSnackBar(
|
||||
context, "You can't choose End Time before Start Time");
|
||||
return;
|
||||
}
|
||||
final setupBookableSpacesBloc =
|
||||
context.read<SetupBookableSpacesBloc>();
|
||||
if (setupBookableSpacesBloc.startTime != null &&
|
||||
isEndTimeAfterStartTime(
|
||||
setupBookableSpacesBloc.startTime!, timePicked)) {
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||
content:
|
||||
Text("You can't choose End Time After Start time"),
|
||||
duration: Duration(seconds: 2),
|
||||
backgroundColor: ColorsManager.red,
|
||||
));
|
||||
throw Exception();
|
||||
} else {
|
||||
setupBookableSpacesBloc.selectedBookableSpaces.forEach(
|
||||
(e) => e.spaceConfig!.bookingEndTime = timePicked,
|
||||
);
|
||||
}
|
||||
|
||||
context.read<SetupBookableSpacesBloc>().add(
|
||||
ChangeEndTimeEvent(endTime: pickedEndTime),
|
||||
);
|
||||
context.read<SetupBookableSpacesBloc>().add(
|
||||
CheckConfigurValidityEvent(),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
Container(
|
||||
width: 50,
|
||||
width: 30,
|
||||
height: 32,
|
||||
alignment: Alignment.center,
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(10),
|
||||
bottomLeft: Radius.circular(10),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: SvgPicture.asset(
|
||||
Assets.clockIcon,
|
||||
height: 15,
|
||||
height: 18,
|
||||
color: ColorsManager.blackColor.withValues(alpha: 0.4),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _showInvalidSnackBar(BuildContext context, String message) {
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message),
|
||||
duration: const Duration(seconds: 2),
|
||||
backgroundColor: ColorsManager.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ class ButtonsDividerBottomDialogWidget extends StatelessWidget {
|
||||
);
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: 1),
|
||||
BookableSpacesParam(currentPage: 1),
|
||||
),
|
||||
);
|
||||
} else if (nonBookableState is NonBookableSpacesError) {
|
||||
@ -79,8 +79,7 @@ class ButtonsDividerBottomDialogWidget extends StatelessWidget {
|
||||
SnackBar(
|
||||
content: Text(
|
||||
nonBookableState.error,
|
||||
style:
|
||||
const TextStyle(color: ColorsManager.red),
|
||||
style: const TextStyle(color: ColorsManager.red),
|
||||
),
|
||||
duration: const Duration(seconds: 2),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
|
@ -2,16 +2,15 @@ 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/setup_bookable_spaces_bloc/setup_bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/custom_checkbox_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class CheckBoxSpaceWidget extends StatelessWidget {
|
||||
final BookableSpacemodel nonBookableSpace;
|
||||
final List<BookableSpacemodel> selectedSpaces;
|
||||
|
||||
const CheckBoxSpaceWidget({
|
||||
super.key,
|
||||
required this.nonBookableSpace,
|
||||
required this.selectedSpaces,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -32,22 +31,25 @@ class CheckBoxSpaceWidget extends StatelessWidget {
|
||||
_ => false,
|
||||
};
|
||||
|
||||
return Checkbox(
|
||||
return CustomCheckboxWidget(
|
||||
value: isChecked,
|
||||
onChanged: (value) {
|
||||
final bloc = context.read<SetupBookableSpacesBloc>();
|
||||
|
||||
if (value ?? false) {
|
||||
bloc.add(AddToBookableSpaceEvent(
|
||||
nonBookableSpace: nonBookableSpace));
|
||||
nonBookableSpace: nonBookableSpace,
|
||||
));
|
||||
} else {
|
||||
bloc.add(RemoveFromBookableSpaceEvent(
|
||||
bookableSpace: nonBookableSpace));
|
||||
bookableSpace: nonBookableSpace,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
const SizedBox(width: 15),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -55,15 +57,17 @@ class CheckBoxSpaceWidget extends StatelessWidget {
|
||||
Text(
|
||||
nonBookableSpace.spaceName,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: ColorsManager.textGray,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 12,
|
||||
color: ColorsManager.titleGray,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
nonBookableSpace.spaceVirtualAddress,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: ColorsManager.textGray,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 10,
|
||||
color: ColorsManager.titleGray,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -0,0 +1,53 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class CustomCheckboxWidget extends StatelessWidget {
|
||||
final bool value;
|
||||
final ValueChanged<bool?> onChanged;
|
||||
final double? outHeight;
|
||||
final double? outWidth;
|
||||
final double? iconSize;
|
||||
const CustomCheckboxWidget({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
this.outWidth,
|
||||
this.outHeight,
|
||||
this.iconSize,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => onChanged(!value),
|
||||
child: Container(
|
||||
width: outWidth ?? 17,
|
||||
height: outHeight ?? 17,
|
||||
decoration: BoxDecoration(
|
||||
color: value ? Colors.white : ColorsManager.checkBoxFillColor,
|
||||
border: value
|
||||
? Border.all(color: ColorsManager.secondaryColor, width: 1)
|
||||
: Border.all(color: ColorsManager.checkBoxBorderGray, width: 1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: value
|
||||
? Center(
|
||||
child: Container(
|
||||
width: outWidth != null ? outWidth! - 4 : 13,
|
||||
height: outHeight != null ? outHeight! - 4 : 13,
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.secondaryColor,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.check,
|
||||
size: 12,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -25,7 +25,6 @@ class DetailsStepsWidget extends StatelessWidget {
|
||||
pointsController: pointsController,
|
||||
editingBookableSpace: editingBookableSpace,
|
||||
),
|
||||
StepsInitial() => const SizedBox(),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
@ -20,38 +20,53 @@ class EditBookableSpaceButtonWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: ElevatedButton(
|
||||
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,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
offset: Offset.zero,
|
||||
blurRadius: 3,
|
||||
spreadRadius: 0,
|
||||
color: ColorsManager.timePickerColor.withValues(
|
||||
alpha: 0.3,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
fixedSize: const Size(50, 30),
|
||||
elevation: 1,
|
||||
)
|
||||
],
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
Assets.settings,
|
||||
height: 15,
|
||||
color: ColorsManager.blue1,
|
||||
child: ElevatedButton(
|
||||
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,
|
||||
minimumSize: const Size(45, 30),
|
||||
elevation: 0,
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
Assets.settings,
|
||||
height: 13,
|
||||
color: ColorsManager.blue1,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -17,7 +17,6 @@ class PaginationButtonsWidget extends StatelessWidget {
|
||||
|
||||
List<Widget> paginationItems = [];
|
||||
|
||||
// « Two pages back
|
||||
if (currentPage > 2) {
|
||||
paginationItems.add(
|
||||
_buildArrowButton(
|
||||
@ -25,7 +24,7 @@ class PaginationButtonsWidget extends StatelessWidget {
|
||||
onTap: () {
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: currentPage - 2),
|
||||
BookableSpacesParam(currentPage: currentPage - 2),
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -33,7 +32,6 @@ class PaginationButtonsWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
// < One page back
|
||||
if (currentPage > 1) {
|
||||
paginationItems.add(
|
||||
_buildArrowButton(
|
||||
@ -41,7 +39,7 @@ class PaginationButtonsWidget extends StatelessWidget {
|
||||
onTap: () {
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: currentPage - 1),
|
||||
BookableSpacesParam(currentPage: currentPage - 1),
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -49,7 +47,6 @@ class PaginationButtonsWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
// Page numbers
|
||||
for (int i = 1; i <= totalPages; i++) {
|
||||
paginationItems.add(
|
||||
Padding(
|
||||
@ -59,7 +56,7 @@ class PaginationButtonsWidget extends StatelessWidget {
|
||||
if (i != currentPage) {
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: i),
|
||||
BookableSpacesParam(currentPage: i),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -69,11 +66,13 @@ class PaginationButtonsWidget extends StatelessWidget {
|
||||
height: 30,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: i == currentPage
|
||||
? ColorsManager.dialogBlueTitle
|
||||
: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
color: i == currentPage
|
||||
? ColorsManager.dialogBlueTitle
|
||||
: ColorsManager.whiteColors,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: ColorsManager.lightGrayBorderColor,
|
||||
)),
|
||||
child: Text(
|
||||
'$i',
|
||||
style: TextStyle(
|
||||
@ -89,7 +88,6 @@ class PaginationButtonsWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
// > One page forward
|
||||
if (currentPage < totalPages) {
|
||||
paginationItems.add(
|
||||
_buildArrowButton(
|
||||
@ -97,7 +95,7 @@ class PaginationButtonsWidget extends StatelessWidget {
|
||||
onTap: () {
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: currentPage + 1),
|
||||
BookableSpacesParam(currentPage: currentPage + 1),
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -105,7 +103,6 @@ class PaginationButtonsWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
// » Two pages forward
|
||||
if (currentPage + 1 < totalPages) {
|
||||
paginationItems.add(
|
||||
_buildArrowButton(
|
||||
@ -113,7 +110,7 @@ class PaginationButtonsWidget extends StatelessWidget {
|
||||
onTap: () {
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: currentPage + 2),
|
||||
BookableSpacesParam(currentPage: currentPage + 2),
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -139,11 +136,15 @@ class PaginationButtonsWidget extends StatelessWidget {
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
width: 30,
|
||||
height: 30,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
color: ColorsManager.whiteColors,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: ColorsManager.lightGrayBorderColor,
|
||||
)),
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
|
@ -28,7 +28,7 @@ class TableOfBookableSpacesWidget extends StatelessWidget {
|
||||
onPressed: () => context
|
||||
.read<BookableSpacesBloc>()
|
||||
.add(LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: 1),
|
||||
BookableSpacesParam(currentPage: 1),
|
||||
)),
|
||||
child: const Text('Try Again'))
|
||||
]);
|
||||
@ -41,12 +41,9 @@ class TableOfBookableSpacesWidget extends StatelessWidget {
|
||||
title: space.spaceName,
|
||||
),
|
||||
),
|
||||
DataCell(Padding(
|
||||
padding: const EdgeInsetsGeometry.only(left: 10),
|
||||
child: Text(
|
||||
space.spaceVirtualAddress,
|
||||
style: const TextStyle(fontSize: 11),
|
||||
))),
|
||||
DataCell(DataCellWidget(
|
||||
title: space.spaceVirtualAddress,
|
||||
)),
|
||||
DataCell(Container(
|
||||
padding: const EdgeInsetsGeometry.only(left: 10),
|
||||
width: 200,
|
||||
|
@ -24,20 +24,37 @@ class RowOfButtonsTitleWidget extends StatelessWidget {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
offset: Offset.zero,
|
||||
blurRadius: 3,
|
||||
spreadRadius: 0,
|
||||
color: ColorsManager.timePickerColor.withValues(
|
||||
alpha: 0.3,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
minimumSize: const Size(50, 40),
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
Assets.backButtonIcon,
|
||||
height: 15,
|
||||
),
|
||||
onPressed: () {
|
||||
pageController.jumpToPage(1);
|
||||
}),
|
||||
child: SvgPicture.asset(
|
||||
Assets.backButtonIcon,
|
||||
height: 15,
|
||||
),
|
||||
onPressed: () {
|
||||
pageController.jumpToPage(1);
|
||||
}),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
@ -53,8 +70,7 @@ class RowOfButtonsTitleWidget extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
SvgTextButton(
|
||||
verticalPadding: 10,
|
||||
horizontalPadding: 10,
|
||||
padding: const EdgeInsets.all(10),
|
||||
svgSize: 15,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
@ -1,32 +1,42 @@
|
||||
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/setup_bookable_spaces_bloc/setup_bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/steps_cubit/cubit/steps_cubit.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/buttons_divider_bottom_dialog_widget.dart';
|
||||
|
||||
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();
|
||||
context
|
||||
.read<SetupBookableSpacesBloc>()
|
||||
.add(CheckConfigurValidityEvent());
|
||||
},
|
||||
onCancelPressed: () => context.pop(),
|
||||
return BlocBuilder<SetupBookableSpacesBloc, SetupBookableSpacesState>(
|
||||
builder: (context, state) {
|
||||
return switch (state) {
|
||||
SetupBookableSpacesInitial() => ButtonsDividerBottomDialogWidget(
|
||||
title: 'Next',
|
||||
onNextPressed: null,
|
||||
onCancelPressed: () => context.pop(),
|
||||
),
|
||||
AddNonBookableSpaceIntoBookableState(:final bookableSpaces) ||
|
||||
RemoveBookableSpaceIntoNonBookableState(:final bookableSpaces) =>
|
||||
ButtonsDividerBottomDialogWidget(
|
||||
title: 'Next',
|
||||
onNextPressed: bookableSpaces.isEmpty
|
||||
? null
|
||||
: () {
|
||||
context.read<StepsCubit>().goToNextStep();
|
||||
context.read<SetupBookableSpacesBloc>().add(
|
||||
CheckConfigurValidityEvent(),
|
||||
);
|
||||
},
|
||||
onCancelPressed: () => context.pop(),
|
||||
),
|
||||
_ => const SizedBox(),
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -9,14 +9,14 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class PointsPartWidget extends StatefulWidget {
|
||||
final BookableSpacemodel? editingBookableSpace;
|
||||
final TextEditingController pointsController;
|
||||
|
||||
const PointsPartWidget({
|
||||
super.key,
|
||||
required this.pointsController,
|
||||
this.editingBookableSpace,
|
||||
});
|
||||
|
||||
final TextEditingController pointsController;
|
||||
|
||||
@override
|
||||
State<PointsPartWidget> createState() => _PointsPartWidgetState();
|
||||
}
|
||||
@ -24,26 +24,28 @@ class PointsPartWidget extends StatefulWidget {
|
||||
class _PointsPartWidgetState extends State<PointsPartWidget> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
if (widget.editingBookableSpace != null) {
|
||||
widget.pointsController.text =
|
||||
widget.editingBookableSpace!.spaceConfig!.cost.toString();
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<TogglePointsSwitchCubit, TogglePointsSwitchState>(
|
||||
builder: (context, state) {
|
||||
builder: (context, switchState) {
|
||||
final isSwitchOn = switchState is ActivatePointsSwitch;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
if (state is ActivatePointsSwitch)
|
||||
if (isSwitchOn)
|
||||
Text(
|
||||
'* ',
|
||||
style: Theme.of(context)
|
||||
@ -52,85 +54,62 @@ class _PointsPartWidgetState extends State<PointsPartWidget> {
|
||||
.copyWith(color: Colors.red),
|
||||
)
|
||||
else
|
||||
const SizedBox(
|
||||
width: 11,
|
||||
),
|
||||
const SizedBox(width: 11),
|
||||
const Text('Points/hrs'),
|
||||
],
|
||||
),
|
||||
Transform.scale(
|
||||
scale: 0.7,
|
||||
child: Switch(
|
||||
trackOutlineColor: WidgetStateProperty.resolveWith<Color>(
|
||||
(Set<WidgetState> states) {
|
||||
return ColorsManager.whiteColors;
|
||||
}),
|
||||
activeTrackColor: ColorsManager.blueColor,
|
||||
inactiveTrackColor: ColorsManager.grayBorder,
|
||||
thumbColor: WidgetStateProperty.resolveWith<Color>(
|
||||
(Set<WidgetState> states) {
|
||||
return ColorsManager.whiteColors;
|
||||
}),
|
||||
value: context.watch<TogglePointsSwitchCubit>().switchValue,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
trackOutlineColor:
|
||||
WidgetStateProperty.all(ColorsManager.whiteColors),
|
||||
activeTrackColor: ColorsManager.dialogBlueTitle,
|
||||
inactiveTrackColor: ColorsManager.lightGrayBorderColor,
|
||||
thumbColor:
|
||||
WidgetStateProperty.all(ColorsManager.whiteColors),
|
||||
value: isSwitchOn,
|
||||
onChanged: (value) {
|
||||
final toggleCubit =
|
||||
context.read<TogglePointsSwitchCubit>();
|
||||
final bloc = context.read<SetupBookableSpacesBloc>();
|
||||
|
||||
final updatedCost = value ? -1 : 0;
|
||||
|
||||
if (value) {
|
||||
context
|
||||
.read<TogglePointsSwitchCubit>()
|
||||
.activateSwitch();
|
||||
context
|
||||
.read<SetupBookableSpacesBloc>()
|
||||
.selectedBookableSpaces
|
||||
.forEach(
|
||||
(e) => e.spaceConfig!.cost = -1,
|
||||
);
|
||||
context
|
||||
.read<SetupBookableSpacesBloc>()
|
||||
.add(CheckConfigurValidityEvent());
|
||||
toggleCubit.activateSwitch();
|
||||
} else {
|
||||
context
|
||||
.read<TogglePointsSwitchCubit>()
|
||||
.unActivateSwitch();
|
||||
toggleCubit.unActivateSwitch();
|
||||
widget.pointsController.clear();
|
||||
context
|
||||
.read<SetupBookableSpacesBloc>()
|
||||
.selectedBookableSpaces
|
||||
.forEach(
|
||||
(e) => e.spaceConfig!.cost = 0,
|
||||
);
|
||||
context
|
||||
.read<SetupBookableSpacesBloc>()
|
||||
.add(CheckConfigurValidityEvent());
|
||||
}
|
||||
bloc.add(ChangeCostEvent(cost: updatedCost));
|
||||
bloc.add(CheckConfigurValidityEvent());
|
||||
},
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
if (state is ActivatePointsSwitch)
|
||||
const SizedBox(height: 5),
|
||||
if (isSwitchOn)
|
||||
SearchUnbookableSpacesWidget(
|
||||
title: 'Ex: 0',
|
||||
height: 40,
|
||||
onChanged: (p0) {
|
||||
context
|
||||
.read<SetupBookableSpacesBloc>()
|
||||
.selectedBookableSpaces
|
||||
.forEach(
|
||||
(e) => e.spaceConfig!.cost = int.parse(
|
||||
widget.pointsController.text.isEmpty
|
||||
? '0'
|
||||
: widget.pointsController.text,
|
||||
),
|
||||
);
|
||||
context
|
||||
.read<SetupBookableSpacesBloc>()
|
||||
.add(CheckConfigurValidityEvent());
|
||||
},
|
||||
topPadding: 0,
|
||||
blur: 1,
|
||||
raduis: 10,
|
||||
height: 34,
|
||||
controller: widget.pointsController,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
suffix: const SizedBox(),
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
onChanged: (_) {
|
||||
final updatedCost =
|
||||
int.tryParse(widget.pointsController.text) ?? 0;
|
||||
context
|
||||
.read<SetupBookableSpacesBloc>()
|
||||
.add(ChangeCostEvent(cost: updatedCost));
|
||||
context.read<SetupBookableSpacesBloc>().add(
|
||||
CheckConfigurValidityEvent(),
|
||||
);
|
||||
},
|
||||
)
|
||||
else
|
||||
const SizedBox(),
|
||||
|
@ -4,70 +4,74 @@ 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/send_bookable_spaces_bloc/send_bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/setup_bookable_spaces_bloc/setup_bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/update_bookable_spaces/update_bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/buttons_divider_bottom_dialog_widget.dart';
|
||||
|
||||
class SaveSecondStepButton extends StatelessWidget {
|
||||
final List<BookableSpacemodel> selectedSpaces;
|
||||
final TextEditingController pointsController;
|
||||
final bool isEditingMode;
|
||||
final List<BookableSpacemodel> bookableSpaces;
|
||||
|
||||
const SaveSecondStepButton({
|
||||
super.key,
|
||||
required this.selectedSpaces,
|
||||
required this.pointsController,
|
||||
required this.isEditingMode,
|
||||
required this.bookableSpaces,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocConsumer<SetupBookableSpacesBloc, SetupBookableSpacesState>(
|
||||
return BlocListener<SendBookableSpacesBloc, SendBookableSpacesState>(
|
||||
listener: (context, state) {
|
||||
if (state is SendBookableSpacesSuccess) {
|
||||
context.read<NonBookableSpacesBloc>().add(CallInitStateEvent());
|
||||
} else if (state is SendBookableSpacesError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.error),
|
||||
),
|
||||
SnackBar(content: Text(state.error)),
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return ButtonsDividerBottomDialogWidget(
|
||||
title: 'Save',
|
||||
onNextPressed: state is UnValidSaveButtonState
|
||||
? null
|
||||
: () {
|
||||
if (selectedSpaces.any(
|
||||
(element) => element.isValid,
|
||||
)) {
|
||||
isEditingMode
|
||||
? callEditLogic(context)
|
||||
: context.read<SetupBookableSpacesBloc>().add(
|
||||
SendBookableSpacesToApi(),
|
||||
child: BlocBuilder<SetupBookableSpacesBloc, SetupBookableSpacesState>(
|
||||
builder: (context, state) {
|
||||
return ButtonsDividerBottomDialogWidget(
|
||||
title: 'Save',
|
||||
onNextPressed: state is UnValidSaveButtonState
|
||||
? null
|
||||
: () {
|
||||
if (bookableSpaces.any((e) => e.isValid)) {
|
||||
if (isEditingMode) {
|
||||
callEditLogic(context);
|
||||
} else {
|
||||
context.read<SendBookableSpacesBloc>().add(
|
||||
SendBookableSpacesToApi(
|
||||
selectedBookableSpaces: bookableSpaces,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
onCancelPressed: () => context.pop(),
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
onCancelPressed: () => context.pop(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void callEditLogic(BuildContext context) {
|
||||
context.read<UpdateBookableSpacesBloc>().add(
|
||||
UpdateBookableSpace(
|
||||
onSuccess: () =>
|
||||
context.read<NonBookableSpacesBloc>().add(CallInitStateEvent()),
|
||||
updatedParams: UpdateBookableSpaceParam.fromBookableModel(
|
||||
context
|
||||
.read<SetupBookableSpacesBloc>()
|
||||
.selectedBookableSpaces
|
||||
.first,
|
||||
print(bookableSpaces.first.spaceConfig!.cost);
|
||||
if (bookableSpaces.isNotEmpty) {
|
||||
context.read<UpdateBookableSpacesBloc>().add(
|
||||
UpdateBookableSpace(
|
||||
onSuccess: () => context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.add(CallInitStateEvent()),
|
||||
updatedParam: UpdateBookableSpaceParam.fromBookableModel(
|
||||
bookableSpaces.first,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class SearchUnbookableSpacesWidget extends StatelessWidget {
|
||||
final String title;
|
||||
final Widget? suffix;
|
||||
final double? height;
|
||||
final double? width;
|
||||
final double? blur;
|
||||
final double? raduis;
|
||||
final double? topPadding;
|
||||
final TextEditingController? controller;
|
||||
final List<TextInputFormatter>? inputFormatters;
|
||||
final void Function(String)? onChanged;
|
||||
const SearchUnbookableSpacesWidget({
|
||||
required this.title,
|
||||
this.controller,
|
||||
this.blur,
|
||||
this.onChanged,
|
||||
this.suffix,
|
||||
this.height,
|
||||
this.width,
|
||||
this.topPadding,
|
||||
this.raduis,
|
||||
this.inputFormatters,
|
||||
super.key,
|
||||
});
|
||||
@ -25,16 +33,18 @@ class SearchUnbookableSpacesWidget extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: width ?? 480,
|
||||
height: height ?? 30,
|
||||
height: height ?? 40,
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.whiteColors,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [
|
||||
borderRadius: BorderRadius.circular(raduis ?? 15),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: ColorsManager.shadowOfSearchTextfield,
|
||||
offset: Offset(0, 4),
|
||||
blurRadius: 5,
|
||||
color:
|
||||
ColorsManager.shadowOfSearchTextfield.withValues(alpha: 0.15),
|
||||
offset: Offset.zero,
|
||||
blurRadius: blur ?? 5,
|
||||
spreadRadius: 0,
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -43,14 +53,21 @@ class SearchUnbookableSpacesWidget extends StatelessWidget {
|
||||
inputFormatters: inputFormatters,
|
||||
onChanged: onChanged,
|
||||
decoration: InputDecoration(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
vertical: topPadding ?? 5,
|
||||
horizontal: 15,
|
||||
),
|
||||
hintText: title,
|
||||
hintStyle: const TextStyle(color: ColorsManager.hintTextGrey),
|
||||
border: InputBorder.none,
|
||||
suffixIcon: suffix ??
|
||||
const Icon(Icons.search,
|
||||
size: 20, color: ColorsManager.hintTextGrey),
|
||||
suffixIcon: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: suffix ??
|
||||
SvgPicture.asset(
|
||||
Assets.searchIcon,
|
||||
height: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
|
@ -26,7 +26,7 @@ class StepTwoDetailsWidget extends StatelessWidget {
|
||||
editingBookableSpace: editingBookableSpace,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
height: 30,
|
||||
),
|
||||
BookingPeriodWidget(
|
||||
editingBookableSpace: editingBookableSpace,
|
||||
|
@ -26,7 +26,7 @@ class StepperPartWidget extends StatelessWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.only(left: 3),
|
||||
alignment: Alignment.centerLeft,
|
||||
height: 50,
|
||||
height: 40,
|
||||
child: const VerticalDivider(
|
||||
width: 8,
|
||||
)),
|
||||
@ -59,7 +59,7 @@ class StepperPartWidget extends StatelessWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.only(left: 3),
|
||||
alignment: Alignment.centerLeft,
|
||||
height: 50,
|
||||
height: 40,
|
||||
child: const VerticalDivider(
|
||||
width: 8,
|
||||
)),
|
||||
|
@ -12,7 +12,7 @@ class TimePickerWidget extends StatefulWidget {
|
||||
required this.onTimePicked,
|
||||
required this.title,
|
||||
});
|
||||
late SetupBookableSpacesBloc setupBookableSpacesBloc;
|
||||
late final SetupBookableSpacesBloc setupBookableSpacesBloc;
|
||||
final void Function(TimeOfDay? timePicked) onTimePicked;
|
||||
@override
|
||||
State<TimePickerWidget> createState() => _TimePickerWidgetState();
|
||||
@ -47,13 +47,17 @@ class _TimePickerWidgetState extends State<TimePickerWidget> {
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (tempTime == null) return;
|
||||
|
||||
widget.onTimePicked(tempTime);
|
||||
timePicked = tempTime;
|
||||
|
||||
widget.setupBookableSpacesBloc.add(CheckConfigurValidityEvent());
|
||||
|
||||
setState(() {});
|
||||
},
|
||||
child: Container(
|
||||
width: 100,
|
||||
height: 32,
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
|
@ -36,9 +36,7 @@ class UnbookableListWidget extends StatelessWidget {
|
||||
if (index < nonBookableSpaces.data.length) {
|
||||
return CheckBoxSpaceWidget(
|
||||
nonBookableSpace: nonBookableSpaces.data[index],
|
||||
selectedSpaces: context
|
||||
.read<SetupBookableSpacesBloc>()
|
||||
.selectedBookableSpaces,
|
||||
|
||||
);
|
||||
} else {
|
||||
return const Padding(
|
||||
|
@ -2,6 +2,7 @@ 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/setup_bookable_spaces_bloc/setup_bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/custom_checkbox_widget.dart';
|
||||
|
||||
class WeekDaysCheckboxRow extends StatefulWidget {
|
||||
final BookableSpacemodel? editingBookableSpace;
|
||||
@ -24,6 +25,7 @@ class _WeekDaysCheckboxRowState extends State<WeekDaysCheckboxRow> {
|
||||
'Sat': false,
|
||||
'Sun': false,
|
||||
};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -40,45 +42,66 @@ class _WeekDaysCheckboxRowState extends State<WeekDaysCheckboxRow> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
return Column(
|
||||
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;
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'* ',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(color: Colors.red),
|
||||
),
|
||||
const Text('Days'),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _daysChecked.entries.map((entry) {
|
||||
return Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
CustomCheckboxWidget(
|
||||
outHeight: 16,
|
||||
outWidth: 16,
|
||||
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<SetupBookableSpacesBloc>()
|
||||
.selectedBookableSpaces) {
|
||||
space.spaceConfig!.bookableDays = selectedDays;
|
||||
}
|
||||
});
|
||||
|
||||
context
|
||||
.read<SetupBookableSpacesBloc>()
|
||||
.add(CheckConfigurValidityEvent());
|
||||
},
|
||||
),
|
||||
context.read<SetupBookableSpacesBloc>().add(
|
||||
AddBookableDaysEvent(bookableDays: selectedDays),
|
||||
);
|
||||
context.read<SetupBookableSpacesBloc>().add(
|
||||
CheckConfigurValidityEvent(),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
entry.key,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
entry.key,
|
||||
style: const TextStyle(fontSize: 10),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -53,8 +53,9 @@ class DeviceManagementBloc
|
||||
for (var community in spaceBloc.state.selectedCommunities) {
|
||||
final spacesList =
|
||||
spaceBloc.state.selectedCommunityAndSpaces[community] ?? [];
|
||||
devices.addAll(await DevicesManagementApi()
|
||||
.fetchDevices(projectUuid, spacesId: spacesList));
|
||||
devices.addAll(await DevicesManagementApi().fetchDevices(projectUuid,
|
||||
spacesId: spacesList,
|
||||
communities: spaceBloc.state.selectedCommunities));
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,7 +159,8 @@ class DeviceManagementBloc
|
||||
add(FilterDevices(_getFilterFromIndex(_selectedIndex)));
|
||||
}
|
||||
|
||||
void _onSelectDevice(SelectDevice event, Emitter<DeviceManagementState> emit) {
|
||||
void _onSelectDevice(
|
||||
SelectDevice event, Emitter<DeviceManagementState> emit) {
|
||||
final selectedUuid = event.selectedDevice.uuid;
|
||||
|
||||
if (_selectedDevices.any((device) => device.uuid == selectedUuid)) {
|
||||
@ -254,7 +256,8 @@ class DeviceManagementBloc
|
||||
_onlineCount = _devices.where((device) => device.online == true).length;
|
||||
_offlineCount = _devices.where((device) => device.online == false).length;
|
||||
_lowBatteryCount = _devices
|
||||
.where((device) => device.batteryLevel != null && device.batteryLevel! < 20)
|
||||
.where((device) =>
|
||||
device.batteryLevel != null && device.batteryLevel! < 20)
|
||||
.length;
|
||||
}
|
||||
|
||||
@ -271,7 +274,8 @@ class DeviceManagementBloc
|
||||
}
|
||||
}
|
||||
|
||||
void _onSearchDevices(SearchDevices event, Emitter<DeviceManagementState> emit) {
|
||||
void _onSearchDevices(
|
||||
SearchDevices event, Emitter<DeviceManagementState> emit) {
|
||||
if ((event.community == null || event.community!.isEmpty) &&
|
||||
(event.unitName == null || event.unitName!.isEmpty) &&
|
||||
(event.deviceNameOrProductName == null ||
|
||||
@ -435,8 +439,8 @@ class DeviceManagementBloc
|
||||
final selectedDevices = loaded.selectedDevice?.map((device) {
|
||||
if (device.uuid == event.deviceId) {
|
||||
return device.copyWith(
|
||||
subspace:
|
||||
device.subspace?.copyWith(subspaceName: event.newSubSpaceName));
|
||||
subspace: device.subspace
|
||||
?.copyWith(subspaceName: event.newSubSpaceName));
|
||||
}
|
||||
return device;
|
||||
}).toList();
|
||||
|
@ -6,6 +6,7 @@ import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/helper/garage_door_helper.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/garage_door/schedule_view/schedule_garage_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
@ -94,11 +95,18 @@ class GarageDoorControlView extends StatelessWidget
|
||||
FetchGarageDoorSchedulesEvent(
|
||||
deviceId: deviceId, category: 'doorcontact_state'),
|
||||
);
|
||||
showDialog(
|
||||
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (ctx) => BlocProvider.value(
|
||||
value: BlocProvider.of<GarageDoorBloc>(context),
|
||||
child: BuildGarageDoorScheduleView(status: status),
|
||||
child: BuildScheduleView(
|
||||
deviceUuid: deviceId,
|
||||
category: 'Timer',
|
||||
code: 'doorcontact_state',
|
||||
countdownCode: 'Timer',
|
||||
deviceType: 'GD',
|
||||
),
|
||||
));
|
||||
},
|
||||
name: 'Scheduling',
|
||||
|
@ -100,6 +100,7 @@ class _DeviceItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return DeviceControlsContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
@ -287,7 +287,8 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
|
||||
try {
|
||||
if (state is ScheduleLoaded) {
|
||||
Status status = Status(code: '', value: '');
|
||||
if (event.deviceType == 'CUR_2') {
|
||||
if (event.deviceType == 'CUR_2' ||
|
||||
event.deviceType == 'GD' ) {
|
||||
status = status.copyWith(
|
||||
code: 'control',
|
||||
value: event.functionOn == true ? 'open' : 'close');
|
||||
|
@ -69,7 +69,7 @@ class CountdownModeButtons extends StatelessWidget {
|
||||
countDownCode: countDownCode),
|
||||
);
|
||||
},
|
||||
backgroundColor: ColorsManager.primaryColorWithOpacity,
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
child: const Text('Save'),
|
||||
),
|
||||
),
|
||||
|
@ -63,7 +63,7 @@ class InchingModeButtons extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
},
|
||||
backgroundColor: ColorsManager.primaryColor,
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
child: const Text('Save'),
|
||||
),
|
||||
),
|
||||
|
@ -31,11 +31,12 @@ class BuildScheduleView extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => ScheduleBloc(deviceId: deviceUuid,)
|
||||
create: (_) => ScheduleBloc(
|
||||
deviceId: deviceUuid,
|
||||
)
|
||||
..add(ScheduleGetEvent(category: category))
|
||||
..add(ScheduleFetchStatusEvent(
|
||||
deviceId: deviceUuid,
|
||||
countdownCode: countdownCode ?? '')),
|
||||
deviceId: deviceUuid, countdownCode: countdownCode ?? '')),
|
||||
child: Dialog(
|
||||
backgroundColor: Colors.white,
|
||||
insetPadding: const EdgeInsets.all(20),
|
||||
@ -56,7 +57,7 @@ class BuildScheduleView extends StatelessWidget {
|
||||
children: [
|
||||
const ScheduleHeader(),
|
||||
const SizedBox(height: 20),
|
||||
if (deviceType == 'CUR_2')
|
||||
if (deviceType == 'CUR_2' || deviceType == 'GD')
|
||||
const SizedBox()
|
||||
else
|
||||
ScheduleModeSelector(
|
||||
@ -76,8 +77,7 @@ class BuildScheduleView extends StatelessWidget {
|
||||
category: category,
|
||||
time: '',
|
||||
function: Status(
|
||||
code: code.toString(),
|
||||
value: true),
|
||||
code: code.toString(), value: true),
|
||||
days: [],
|
||||
),
|
||||
isEdit: false,
|
||||
@ -96,7 +96,7 @@ class BuildScheduleView extends StatelessWidget {
|
||||
}
|
||||
},
|
||||
),
|
||||
if (deviceType != 'CUR_2')
|
||||
if (deviceType != 'CUR_2'|| deviceType != 'GD')
|
||||
if (state.scheduleMode == ScheduleModes.countdown ||
|
||||
state.scheduleMode == ScheduleModes.inching)
|
||||
CountdownInchingView(
|
||||
|
@ -24,12 +24,13 @@ class ScheduleManagementUI extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 170,
|
||||
width: 177,
|
||||
height: 40,
|
||||
child: DefaultButton(
|
||||
borderColor: ColorsManager.grayColor.withOpacity(0.5),
|
||||
padding: 2,
|
||||
backgroundColor: ColorsManager.graysColor,
|
||||
borderWidth: 4,
|
||||
borderColor: ColorsManager.neutralGray,
|
||||
padding: 8,
|
||||
backgroundColor: ColorsManager.textFieldGreyColor,
|
||||
borderRadius: 15,
|
||||
onPressed: onAddSchedule,
|
||||
child: Row(
|
||||
|
@ -39,7 +39,7 @@ class ScheduleModeButtons extends StatelessWidget {
|
||||
borderRadius: 8,
|
||||
height: 40,
|
||||
onPressed: onSave,
|
||||
backgroundColor: ColorsManager.primaryColorWithOpacity,
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
child: const Text('Save'),
|
||||
),
|
||||
),
|
||||
|
@ -194,7 +194,7 @@ class _ScheduleTableView extends StatelessWidget {
|
||||
child: Text(_getSelectedDays(
|
||||
ScheduleModel.parseSelectedDays(schedule.days)))),
|
||||
Center(child: Text(formatIsoStringToTime(schedule.time, context))),
|
||||
if (deviceType == 'CUR_2')
|
||||
if (deviceType == 'CUR_2' || deviceType == 'GD')
|
||||
Center(
|
||||
child: Text(schedule.function.value == true ? 'open' : 'close'))
|
||||
else
|
||||
|
@ -23,7 +23,7 @@ class ScheduleDialogHelper {
|
||||
required String deviceType,
|
||||
}) {
|
||||
bool temp;
|
||||
if (deviceType == 'CUR_2') {
|
||||
if (deviceType == 'CUR_2' || deviceType == 'GD') {
|
||||
temp = schedule!.function.value == 'open' ? true : false;
|
||||
} else {
|
||||
temp = schedule!.function.value;
|
||||
@ -116,7 +116,7 @@ class ScheduleDialogHelper {
|
||||
ScheduleModeButtons(
|
||||
onSave: () {
|
||||
dynamic temp;
|
||||
if (deviceType == 'CUR_2') {
|
||||
if (deviceType == 'CUR_2' || deviceType == 'GD') {
|
||||
temp = functionOn! ? 'open' : 'close';
|
||||
} else {
|
||||
temp = functionOn;
|
||||
@ -202,18 +202,23 @@ class ScheduleDialogHelper {
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Radio<bool>(
|
||||
activeColor: ColorsManager.secondaryColor,
|
||||
focusColor: ColorsManager.secondaryColor,
|
||||
value: true,
|
||||
groupValue: isOn,
|
||||
onChanged: (val) => onChanged(true),
|
||||
),
|
||||
Text(categor == 'CUR_2' ? 'open' : 'On'),
|
||||
Text(categor == 'CUR_2' || categor == 'GD' ? 'open' : 'On'),
|
||||
const SizedBox(width: 10),
|
||||
Radio<bool>(
|
||||
activeColor: ColorsManager.secondaryColor,
|
||||
focusColor: ColorsManager.secondaryColor,
|
||||
|
||||
value: false,
|
||||
groupValue: isOn,
|
||||
onChanged: (val) => onChanged(false),
|
||||
),
|
||||
Text(categor == 'CUR_2' ? 'close' : 'Off'),
|
||||
Text(categor == 'CUR_2' || categor == 'GD' ? 'close' : 'Off'),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -937,13 +937,17 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
List<String> spacesList =
|
||||
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||
|
||||
devices.addAll(await DevicesManagementApi()
|
||||
.fetchDevices(projectUuid, spacesId: spacesList));
|
||||
devices.addAll(await DevicesManagementApi().fetchDevices(
|
||||
projectUuid,
|
||||
spacesId: spacesList,
|
||||
communities: spaceBloc.state.selectedCommunities,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
devices.addAll(await DevicesManagementApi().fetchDevices(
|
||||
projectUuid,
|
||||
spacesId: [createRoutineBloc.selectedSpaceId],
|
||||
communities: spaceBloc.state.selectedCommunities,
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,71 @@
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||
|
||||
abstract final class SpacesRecursiveHelper {
|
||||
const SpacesRecursiveHelper._();
|
||||
|
||||
static List<SpaceModel> recusrivelyUpdate(
|
||||
List<SpaceModel> spaces,
|
||||
SpaceDetailsModel updatedSpace,
|
||||
) {
|
||||
return spaces.map((space) {
|
||||
final isUpdatedSpace = space.uuid == updatedSpace.uuid;
|
||||
if (isUpdatedSpace) {
|
||||
return space.copyWith(
|
||||
spaceName: updatedSpace.spaceName,
|
||||
icon: updatedSpace.icon,
|
||||
);
|
||||
}
|
||||
final hasChildren = space.children.isNotEmpty;
|
||||
if (hasChildren) {
|
||||
return space.copyWith(
|
||||
children: recusrivelyUpdate(space.children, updatedSpace),
|
||||
);
|
||||
}
|
||||
return space;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
static List<SpaceModel> recusrivelyDelete(
|
||||
List<SpaceModel> spaces,
|
||||
String spaceUuid,
|
||||
) {
|
||||
final updatedSpaces = spaces.map((space) {
|
||||
if (space.uuid == spaceUuid) return null;
|
||||
if (space.children.isNotEmpty) {
|
||||
return space.copyWith(
|
||||
children: recusrivelyDelete(space.children, spaceUuid),
|
||||
);
|
||||
}
|
||||
return space;
|
||||
}).toList();
|
||||
final nonNullSpaces = updatedSpaces.whereType<SpaceModel>().toList();
|
||||
return nonNullSpaces;
|
||||
}
|
||||
|
||||
static List<SpaceModel> recursivelyInsert({
|
||||
required List<SpaceModel> spaces,
|
||||
required String parentUuid,
|
||||
required SpaceModel newSpace,
|
||||
}) {
|
||||
return spaces.map((space) {
|
||||
final isParentSpace = space.uuid == parentUuid;
|
||||
if (isParentSpace) {
|
||||
return space.copyWith(
|
||||
children: [...space.children, newSpace],
|
||||
);
|
||||
}
|
||||
final hasChildren = space.children.isNotEmpty;
|
||||
if (hasChildren) {
|
||||
return space.copyWith(
|
||||
children: recursivelyInsert(
|
||||
spaces: space.children,
|
||||
parentUuid: parentUuid,
|
||||
newSpace: newSpace,
|
||||
),
|
||||
);
|
||||
}
|
||||
return space;
|
||||
}).toList();
|
||||
}
|
||||
}
|
@ -5,13 +5,14 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
||||
class SpacesConnectionsArrowPainter extends CustomPainter {
|
||||
final List<SpaceConnectionModel> connections;
|
||||
final Map<String, Offset> positions;
|
||||
final double cardWidth = 150.0;
|
||||
final Map<String, double> cardWidths;
|
||||
final double cardHeight = 90.0;
|
||||
final Set<String> highlightedUuids;
|
||||
|
||||
SpacesConnectionsArrowPainter({
|
||||
required this.connections,
|
||||
required this.positions,
|
||||
required this.cardWidths,
|
||||
required this.highlightedUuids,
|
||||
});
|
||||
|
||||
@ -29,19 +30,30 @@ class SpacesConnectionsArrowPainter extends CustomPainter {
|
||||
|
||||
final from = positions[connection.from];
|
||||
final to = positions[connection.to];
|
||||
final fromWidth = cardWidths[connection.from] ?? 150.0;
|
||||
final toWidth = cardWidths[connection.to] ?? 150.0;
|
||||
|
||||
if (from != null && to != null) {
|
||||
final startPoint =
|
||||
Offset(from.dx + cardWidth / 2, from.dy + cardHeight - 10);
|
||||
final endPoint = Offset(to.dx + cardWidth / 2, to.dy);
|
||||
Offset(from.dx + fromWidth / 2, from.dy + cardHeight - 10);
|
||||
final endPoint = Offset(to.dx + toWidth / 2, to.dy);
|
||||
|
||||
final path = Path()..moveTo(startPoint.dx, startPoint.dy);
|
||||
|
||||
final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 20);
|
||||
final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 60);
|
||||
|
||||
path.cubicTo(controlPoint1.dx, controlPoint1.dy, controlPoint2.dx,
|
||||
controlPoint2.dy, endPoint.dx, endPoint.dy);
|
||||
if ((startPoint.dx - endPoint.dx).abs() < 1.0) {
|
||||
path.lineTo(endPoint.dx, endPoint.dy);
|
||||
} else {
|
||||
final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 100);
|
||||
final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 100);
|
||||
path.cubicTo(
|
||||
controlPoint1.dx,
|
||||
controlPoint1.dy,
|
||||
controlPoint2.dx,
|
||||
controlPoint2.dy,
|
||||
endPoint.dx,
|
||||
endPoint.dy,
|
||||
);
|
||||
}
|
||||
|
||||
canvas.drawPath(path, paint);
|
||||
|
||||
@ -51,7 +63,7 @@ class SpacesConnectionsArrowPainter extends CustomPainter {
|
||||
: ColorsManager.blackColor.withValues(alpha: 0.5)
|
||||
..style = PaintingStyle.fill
|
||||
..blendMode = BlendMode.srcIn;
|
||||
canvas.drawCircle(endPoint, 4, circlePaint);
|
||||
canvas.drawCircle(endPoint, 6, circlePaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,10 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/presen
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/data/services/remote_products_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/presentation/bloc/products_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/data/services/remote_reorder_spaces_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/presentation/bloc/reorder_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
||||
@ -25,15 +27,16 @@ class SpaceManagementPage extends StatefulWidget {
|
||||
|
||||
class _SpaceManagementPageState extends State<SpaceManagementPage> {
|
||||
late final CommunitiesBloc communitiesBloc;
|
||||
late final HTTPService _httpService;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_httpService = HTTPService();
|
||||
communitiesBloc = CommunitiesBloc(
|
||||
communitiesService: DebouncedCommunitiesService(
|
||||
RemoteCommunitiesService(HTTPService()),
|
||||
RemoteCommunitiesService(_httpService),
|
||||
),
|
||||
)..add(const LoadCommunities(LoadCommunitiesParam()));
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -49,14 +52,19 @@ class _SpaceManagementPageState extends State<SpaceManagementPage> {
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => SpaceDetailsBloc(
|
||||
UniqueSubspacesDecorator(
|
||||
RemoteSpaceDetailsService(httpService: HTTPService()),
|
||||
UniqueSpaceDetailsSpacesDecoratorService(
|
||||
RemoteSpaceDetailsService(httpService: _httpService),
|
||||
),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => ProductsBloc(
|
||||
RemoteProductsService(HTTPService()),
|
||||
RemoteProductsService(_httpService),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => ReorderSpacesBloc(
|
||||
RemoteReorderSpacesService(_httpService),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_connection_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_reorder_data_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart';
|
||||
@ -10,6 +11,8 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/params/reorder_spaces_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/presentation/bloc/reorder_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
@ -30,10 +33,11 @@ class CommunityStructureCanvas extends StatefulWidget {
|
||||
class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final Map<String, Offset> _positions = {};
|
||||
final double _cardWidth = 150.0;
|
||||
final Map<String, double> _cardWidths = {};
|
||||
final double _cardHeight = 90.0;
|
||||
final double _horizontalSpacing = 150.0;
|
||||
final double _verticalSpacing = 120.0;
|
||||
static const double _minCardWidth = 150.0;
|
||||
|
||||
late final TransformationController _transformationController;
|
||||
late final AnimationController _animationController;
|
||||
@ -52,6 +56,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
@override
|
||||
void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.selectedSpace == null) return;
|
||||
if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
@ -68,6 +73,34 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
double _calculateCardWidth(String text) {
|
||||
final style = context.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
);
|
||||
final textPainter = TextPainter(
|
||||
text: TextSpan(text: text, style: style),
|
||||
maxLines: 1,
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout();
|
||||
|
||||
const iconWidth = 40.0;
|
||||
const horizontalPadding = 10.0;
|
||||
const contentPadding = 10.0;
|
||||
final calculatedWidth =
|
||||
iconWidth + horizontalPadding + textPainter.width + contentPadding;
|
||||
|
||||
return calculatedWidth.clamp(_minCardWidth, double.infinity);
|
||||
}
|
||||
|
||||
void _calculateAllCardWidths(List<SpaceModel> spaces) {
|
||||
for (final space in spaces) {
|
||||
_cardWidths[space.uuid] = _calculateCardWidth(space.spaceName);
|
||||
if (space.children.isNotEmpty) {
|
||||
_calculateAllCardWidths(space.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> _getAllDescendantUuids(SpaceModel space) {
|
||||
final uuids = <String>{};
|
||||
for (final child in space.children) {
|
||||
@ -102,11 +135,12 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
final position = _positions[space.uuid];
|
||||
if (position == null) return;
|
||||
|
||||
final cardWidth = _cardWidths[space.uuid] ?? _minCardWidth;
|
||||
const scale = 1;
|
||||
final viewSize = context.size;
|
||||
if (viewSize == null) return;
|
||||
|
||||
final x = -position.dx * scale + (viewSize.width / 2) - (_cardWidth * scale / 2);
|
||||
final x = -position.dx * scale + (viewSize.width / 2) - (cardWidth * scale / 2);
|
||||
final y =
|
||||
-position.dy * scale + (viewSize.height / 2) - (_cardHeight * scale / 2);
|
||||
|
||||
@ -132,6 +166,16 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
context.read<CommunitiesBloc>().add(
|
||||
CommunitiesUpdateCommunity(newCommunity),
|
||||
);
|
||||
|
||||
context.read<ReorderSpacesBloc>().add(
|
||||
ReorderSpacesEvent(
|
||||
ReorderSpacesParam(
|
||||
communityUuid: widget.community.uuid,
|
||||
parentSpaceUuid: data.parent?.uuid ?? '',
|
||||
spaces: children,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onSpaceTapped(SpaceModel? space) {
|
||||
@ -155,13 +199,16 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
Map<int, double> levelXOffset,
|
||||
) {
|
||||
for (final space in spaces) {
|
||||
final cardWidth = _cardWidths[space.uuid] ?? _minCardWidth;
|
||||
double childSubtreeWidth = 0;
|
||||
if (space.children.isNotEmpty) {
|
||||
_calculateLayout(space.children, depth + 1, levelXOffset);
|
||||
final firstChildPos = _positions[space.children.first.uuid];
|
||||
final lastChildPos = _positions[space.children.last.uuid];
|
||||
if (firstChildPos != null && lastChildPos != null) {
|
||||
childSubtreeWidth = (lastChildPos.dx + _cardWidth) - firstChildPos.dx;
|
||||
final lastChildWidth =
|
||||
_cardWidths[space.children.last.uuid] ?? _minCardWidth;
|
||||
childSubtreeWidth = (lastChildPos.dx + lastChildWidth) - firstChildPos.dx;
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,7 +217,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
|
||||
if (space.children.isNotEmpty) {
|
||||
final firstChildPos = _positions[space.children.first.uuid]!;
|
||||
x = firstChildPos.dx + (childSubtreeWidth - _cardWidth) / 2;
|
||||
x = firstChildPos.dx + (childSubtreeWidth - cardWidth) / 2;
|
||||
} else {
|
||||
x = currentX;
|
||||
}
|
||||
@ -187,7 +234,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
|
||||
final y = depth * (_verticalSpacing + _cardHeight);
|
||||
_positions[space.uuid] = Offset(x, y);
|
||||
levelXOffset[depth] = x + _cardWidth + _horizontalSpacing;
|
||||
levelXOffset[depth] = x + cardWidth + _horizontalSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
@ -202,11 +249,21 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
|
||||
List<Widget> _buildTreeWidgets() {
|
||||
_positions.clear();
|
||||
_cardWidths.clear();
|
||||
final community = widget.community;
|
||||
|
||||
_calculateAllCardWidths(community.spaces);
|
||||
|
||||
final levelXOffset = <int, double>{};
|
||||
_calculateLayout(community.spaces, 0, levelXOffset);
|
||||
|
||||
const horizontalCanvasPadding = 100.0;
|
||||
final originalPositions = Map.of(_positions);
|
||||
_positions.clear();
|
||||
for (final entry in originalPositions.entries) {
|
||||
_positions[entry.key] = entry.value.translate(horizontalCanvasPadding, 0);
|
||||
}
|
||||
|
||||
final selectedSpace = widget.selectedSpace;
|
||||
final highlightedUuids = <String>{};
|
||||
if (selectedSpace != null) {
|
||||
@ -224,14 +281,14 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
community: widget.community,
|
||||
);
|
||||
|
||||
final createButtonX = levelXOffset[0] ?? 0.0;
|
||||
final createButtonX = (levelXOffset[0] ?? 0.0) + horizontalCanvasPadding;
|
||||
const createButtonY = 0.0;
|
||||
|
||||
widgets.add(
|
||||
Positioned(
|
||||
left: createButtonX,
|
||||
top: createButtonY,
|
||||
child: CreateSpaceButton(communityUuid: widget.community.uuid),
|
||||
child: CreateSpaceButton(community: widget.community),
|
||||
),
|
||||
);
|
||||
|
||||
@ -240,6 +297,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
painter: SpacesConnectionsArrowPainter(
|
||||
connections: connections,
|
||||
positions: _positions,
|
||||
cardWidths: _cardWidths,
|
||||
highlightedUuids: highlightedUuids,
|
||||
),
|
||||
child: Stack(alignment: AlignmentDirectional.center, children: widgets),
|
||||
@ -255,10 +313,12 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
CommunityModel? community,
|
||||
SpaceModel? parent,
|
||||
}) {
|
||||
const targetWidth = 40.0;
|
||||
final padding = (_horizontalSpacing - targetWidth) / 2;
|
||||
if (spaces.isNotEmpty) {
|
||||
final firstChildPos = _positions[spaces.first.uuid]!;
|
||||
final targetPos = Offset(
|
||||
firstChildPos.dx - (_horizontalSpacing / 4),
|
||||
firstChildPos.dx - padding - targetWidth,
|
||||
firstChildPos.dy,
|
||||
);
|
||||
widgets.add(_buildDropTarget(parent, community, 0, targetPos));
|
||||
@ -271,6 +331,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
continue;
|
||||
}
|
||||
|
||||
final cardWidth = _cardWidths[space.uuid] ?? _minCardWidth;
|
||||
final isHighlighted = highlightedUuids.contains(space.uuid);
|
||||
final hasNoSelectedSpace = widget.selectedSpace == null;
|
||||
|
||||
@ -278,20 +339,29 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
buildSpaceContainer: () {
|
||||
return Opacity(
|
||||
opacity: hasNoSelectedSpace || isHighlighted ? 1.0 : 0.5,
|
||||
child: Tooltip(
|
||||
message: space.spaceName,
|
||||
preferBelow: false,
|
||||
child: SpaceCell(
|
||||
onTap: () => _onSpaceTapped(space),
|
||||
icon: space.icon,
|
||||
name: space.spaceName,
|
||||
),
|
||||
child: SpaceCell(
|
||||
onTap: () => _onSpaceTapped(space),
|
||||
icon: space.icon,
|
||||
name: space.spaceName,
|
||||
),
|
||||
);
|
||||
},
|
||||
onTap: () => SpaceDetailsDialogHelper.showCreate(
|
||||
context,
|
||||
communityUuid: widget.community.uuid,
|
||||
parentUuid: space.uuid,
|
||||
onSuccess: (updatedSpaceModel) {
|
||||
final updatedSpaces = SpacesRecursiveHelper.recursivelyInsert(
|
||||
spaces: widget.community.spaces,
|
||||
parentUuid: space.uuid,
|
||||
newSpace: updatedSpaceModel,
|
||||
);
|
||||
context.read<CommunitiesBloc>().add(
|
||||
CommunitiesUpdateCommunity(
|
||||
widget.community.copyWith(spaces: updatedSpaces),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@ -305,7 +375,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
Positioned(
|
||||
left: position.dx,
|
||||
top: position.dy,
|
||||
width: _cardWidth,
|
||||
width: cardWidth,
|
||||
height: _cardHeight,
|
||||
child: Draggable<SpaceReorderDataModel>(
|
||||
data: reorderData,
|
||||
@ -314,7 +384,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
child: Opacity(
|
||||
opacity: 0.2,
|
||||
child: SizedBox(
|
||||
width: _cardWidth,
|
||||
width: cardWidth,
|
||||
height: _cardHeight,
|
||||
child: spaceCard,
|
||||
),
|
||||
@ -330,7 +400,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
);
|
||||
|
||||
final targetPos = Offset(
|
||||
position.dx + _cardWidth + (_horizontalSpacing / 4) - 20,
|
||||
position.dx + cardWidth + padding,
|
||||
position.dy,
|
||||
);
|
||||
widgets.add(_buildDropTarget(parent, community, i + 1, targetPos));
|
||||
@ -365,24 +435,33 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
child: DragTarget<SpaceReorderDataModel>(
|
||||
builder: (context, candidateData, rejectedData) {
|
||||
if (_draggedData == null) {
|
||||
return const SizedBox();
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final isTargetForDragged = (_draggedData?.parent?.uuid == parent?.uuid &&
|
||||
_draggedData?.community == null) ||
|
||||
(_draggedData?.community?.uuid == community?.uuid &&
|
||||
_draggedData?.parent == null);
|
||||
final children = parent?.children ?? community?.spaces ?? [];
|
||||
final isSameParent = (_draggedData!.parent?.uuid == parent?.uuid &&
|
||||
_draggedData!.community == null) ||
|
||||
(_draggedData!.community?.uuid == community?.uuid &&
|
||||
_draggedData!.parent == null);
|
||||
|
||||
if (!isTargetForDragged) {
|
||||
return const SizedBox();
|
||||
if (!isSameParent) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Container(
|
||||
final oldIndex =
|
||||
children.indexWhere((s) => s.uuid == _draggedData!.space.uuid);
|
||||
if (oldIndex != -1 && (oldIndex == index || oldIndex == index - 1)) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
width: 40,
|
||||
alignment: Alignment.center,
|
||||
height: _cardHeight,
|
||||
decoration: BoxDecoration(
|
||||
color: context.theme.colorScheme.primary.withValues(
|
||||
alpha: candidateData.isNotEmpty ? 0.7 : 0.3,
|
||||
alpha: candidateData.isNotEmpty ? 0.9 : 0.3,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
@ -405,6 +484,9 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
|
||||
final oldIndex =
|
||||
children.indexWhere((s) => s.uuid == data.data.space.uuid);
|
||||
if (oldIndex == -1) {
|
||||
return true;
|
||||
}
|
||||
if (oldIndex == index || oldIndex == index - 1) {
|
||||
return false;
|
||||
}
|
||||
@ -418,21 +500,21 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final treeWidgets = _buildTreeWidgets();
|
||||
return InteractiveViewer(
|
||||
transformationController: _transformationController,
|
||||
boundaryMargin: EdgeInsets.symmetric(
|
||||
horizontal: context.screenWidth * 0.3,
|
||||
vertical: context.screenHeight * 0.3,
|
||||
),
|
||||
minScale: 0.5,
|
||||
maxScale: 3.0,
|
||||
constrained: false,
|
||||
child: GestureDetector(
|
||||
onTap: _resetSelectionAndZoom,
|
||||
return GestureDetector(
|
||||
onTap: _resetSelectionAndZoom,
|
||||
child: InteractiveViewer(
|
||||
transformationController: _transformationController,
|
||||
boundaryMargin: EdgeInsets.symmetric(
|
||||
horizontal: context.screenWidth * 0.3,
|
||||
vertical: context.screenHeight * 0.3,
|
||||
),
|
||||
minScale: 0.5,
|
||||
maxScale: 3.0,
|
||||
constrained: false,
|
||||
child: SizedBox(
|
||||
width: context.screenWidth * 5,
|
||||
height: context.screenHeight * 5,
|
||||
child: Stack(children: treeWidgets),
|
||||
child: Stack(clipBehavior: Clip.none, children: treeWidgets),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -2,41 +2,17 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons_composer.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class CommunityStructureHeader extends StatelessWidget {
|
||||
const CommunityStructureHeader({super.key});
|
||||
|
||||
List<SpaceModel> _updateRecursive(
|
||||
List<SpaceModel> spaces,
|
||||
SpaceDetailsModel updatedSpace,
|
||||
) {
|
||||
return spaces.map((space) {
|
||||
if (space.uuid == updatedSpace.uuid) {
|
||||
return space.copyWith(
|
||||
spaceName: updatedSpace.spaceName,
|
||||
icon: updatedSpace.icon,
|
||||
);
|
||||
}
|
||||
if (space.children.isNotEmpty) {
|
||||
return space.copyWith(
|
||||
children: _updateRecursive(space.children, updatedSpace),
|
||||
);
|
||||
}
|
||||
return space;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
|
||||
@ -44,9 +20,9 @@ class CommunityStructureHeader extends StatelessWidget {
|
||||
color: ColorsManager.whiteColors,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: ColorsManager.shadowBlackColor,
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
color: ColorsManager.shadowBlackColor.withValues(alpha: 0.1),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -57,7 +33,7 @@ class CommunityStructureHeader extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildCommunityInfo(context, theme, screenWidth),
|
||||
child: _buildCommunityInfo(context, screenWidth),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
@ -67,8 +43,7 @@ class CommunityStructureHeader extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCommunityInfo(
|
||||
BuildContext context, ThemeData theme, double screenWidth) {
|
||||
Widget _buildCommunityInfo(BuildContext context, double screenWidth) {
|
||||
final selectedCommunity =
|
||||
context.watch<CommunitiesTreeSelectionBloc>().state.selectedCommunity;
|
||||
final selectedSpace =
|
||||
@ -78,7 +53,7 @@ class CommunityStructureHeader extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
'Community Structure',
|
||||
style: theme.textTheme.headlineLarge?.copyWith(
|
||||
style: context.textTheme.headlineLarge?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
@ -91,7 +66,7 @@ class CommunityStructureHeader extends StatelessWidget {
|
||||
Flexible(
|
||||
child: SelectableText(
|
||||
selectedCommunity.name,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
maxLines: 1,
|
||||
@ -115,27 +90,8 @@ class CommunityStructureHeader extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
CommunityStructureHeaderActionButtons(
|
||||
onDelete: (space) {},
|
||||
onDuplicate: (space) {},
|
||||
onEdit: (space) => SpaceDetailsDialogHelper.showEdit(
|
||||
context,
|
||||
spaceModel: selectedSpace!,
|
||||
communityUuid: selectedCommunity.uuid,
|
||||
onSuccess: (updatedSpaceDetails) {
|
||||
final communitiesBloc = context.read<CommunitiesBloc>();
|
||||
final updatedSpaces = _updateRecursive(
|
||||
selectedCommunity.spaces,
|
||||
updatedSpaceDetails,
|
||||
);
|
||||
|
||||
final community = selectedCommunity.copyWith(
|
||||
spaces: updatedSpaces,
|
||||
);
|
||||
|
||||
communitiesBloc.add(CommunitiesUpdateCommunity(community));
|
||||
},
|
||||
),
|
||||
CommunityStructureHeaderActionButtonsComposer(
|
||||
selectedCommunity: selectedCommunity,
|
||||
selectedSpace: selectedSpace,
|
||||
),
|
||||
],
|
||||
|
@ -19,27 +19,27 @@ class CommunityStructureHeaderActionButtons extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (selectedSpace == null) return const SizedBox.shrink();
|
||||
|
||||
return Wrap(
|
||||
alignment: WrapAlignment.end,
|
||||
spacing: 10,
|
||||
children: [
|
||||
if (selectedSpace != null) ...[
|
||||
CommunityStructureHeaderButton(
|
||||
label: 'Edit',
|
||||
svgAsset: Assets.editSpace,
|
||||
onPressed: () => onEdit(selectedSpace!),
|
||||
),
|
||||
CommunityStructureHeaderButton(
|
||||
label: 'Duplicate',
|
||||
svgAsset: Assets.duplicate,
|
||||
onPressed: () => onDuplicate(selectedSpace!),
|
||||
),
|
||||
CommunityStructureHeaderButton(
|
||||
label: 'Delete',
|
||||
svgAsset: Assets.spaceDelete,
|
||||
onPressed: () => onDelete(selectedSpace!),
|
||||
),
|
||||
],
|
||||
CommunityStructureHeaderButton(
|
||||
label: 'Edit',
|
||||
svgAsset: Assets.editSpace,
|
||||
onPressed: () => onEdit(selectedSpace!),
|
||||
),
|
||||
CommunityStructureHeaderButton(
|
||||
label: 'Duplicate',
|
||||
svgAsset: Assets.duplicate,
|
||||
onPressed: () => onDuplicate(selectedSpace!),
|
||||
),
|
||||
CommunityStructureHeaderButton(
|
||||
label: 'Delete',
|
||||
svgAsset: Assets.spaceDelete,
|
||||
onPressed: () => onDelete(selectedSpace!),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,69 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/helpers/spaces_recursive_helper.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
||||
|
||||
class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget {
|
||||
const CommunityStructureHeaderActionButtonsComposer({
|
||||
required this.selectedCommunity,
|
||||
required this.selectedSpace,
|
||||
super.key,
|
||||
});
|
||||
final CommunityModel selectedCommunity;
|
||||
final SpaceModel? selectedSpace;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommunityStructureHeaderActionButtons(
|
||||
onDelete: (space) => showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => DeleteSpaceDialog(
|
||||
space: space,
|
||||
community: selectedCommunity,
|
||||
onSuccess: () {
|
||||
final updatedSpaces = SpacesRecursiveHelper.recusrivelyDelete(
|
||||
selectedCommunity.spaces,
|
||||
space.uuid,
|
||||
);
|
||||
final community = selectedCommunity.copyWith(
|
||||
spaces: updatedSpaces,
|
||||
);
|
||||
context.read<CommunitiesBloc>().add(
|
||||
CommunitiesUpdateCommunity(community),
|
||||
);
|
||||
context.read<CommunitiesTreeSelectionBloc>().add(
|
||||
SelectCommunityEvent(community: selectedCommunity),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
onDuplicate: (space) {},
|
||||
onEdit: (space) => SpaceDetailsDialogHelper.showEdit(
|
||||
context,
|
||||
spaceModel: selectedSpace!,
|
||||
communityUuid: selectedCommunity.uuid,
|
||||
onSuccess: (updatedSpaceDetails) {
|
||||
final communitiesBloc = context.read<CommunitiesBloc>();
|
||||
final updatedSpaces = SpacesRecursiveHelper.recusrivelyUpdate(
|
||||
selectedCommunity.spaces,
|
||||
updatedSpaceDetails,
|
||||
);
|
||||
|
||||
final community = selectedCommunity.copyWith(
|
||||
spaces: updatedSpaces,
|
||||
);
|
||||
|
||||
communitiesBloc.add(CommunitiesUpdateCommunity(community));
|
||||
},
|
||||
),
|
||||
selectedSpace: selectedSpace,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,14 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class CreateSpaceButton extends StatefulWidget {
|
||||
const CreateSpaceButton({
|
||||
required this.communityUuid,
|
||||
required this.community,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String communityUuid;
|
||||
final CommunityModel community;
|
||||
|
||||
@override
|
||||
State<CreateSpaceButton> createState() => _CreateSpaceButtonState();
|
||||
@ -25,7 +29,21 @@ class _CreateSpaceButtonState extends State<CreateSpaceButton> {
|
||||
child: InkWell(
|
||||
onTap: () => SpaceDetailsDialogHelper.showCreate(
|
||||
context,
|
||||
communityUuid: widget.communityUuid,
|
||||
communityUuid: widget.community.uuid,
|
||||
onSuccess: (updatedSpaceModel) {
|
||||
final newCommunity = widget.community.copyWith(
|
||||
spaces: [...widget.community.spaces, updatedSpaceModel],
|
||||
);
|
||||
context.read<CommunitiesBloc>().add(
|
||||
CommunitiesUpdateCommunity(newCommunity),
|
||||
);
|
||||
context.read<CommunitiesTreeSelectionBloc>().add(
|
||||
SelectSpaceEvent(
|
||||
space: updatedSpaceModel,
|
||||
community: newCommunity,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
child: MouseRegion(
|
||||
onEnter: (_) => setState(() => _isHovered = true),
|
||||
|
@ -2,31 +2,22 @@ import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class PlusButtonWidget extends StatelessWidget {
|
||||
final Offset offset;
|
||||
final void Function() onButtonTap;
|
||||
final void Function() onTap;
|
||||
|
||||
const PlusButtonWidget({
|
||||
required this.onTap,
|
||||
super.key,
|
||||
required this.offset,
|
||||
required this.onButtonTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onButtonTap,
|
||||
child: Container(
|
||||
width: 30,
|
||||
height: 30,
|
||||
decoration: const BoxDecoration(
|
||||
color: ColorsManager.spaceColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.add,
|
||||
color: ColorsManager.whiteColors,
|
||||
size: 20,
|
||||
),
|
||||
return IconButton.filled(
|
||||
onPressed: onTap,
|
||||
style: IconButton.styleFrom(backgroundColor: ColorsManager.spaceColor),
|
||||
icon: const Icon(
|
||||
Icons.add,
|
||||
color: ColorsManager.whiteColors,
|
||||
size: 20,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -29,10 +29,9 @@ class _SpaceCardWidgetState extends State<SpaceCardWidget> {
|
||||
widget.buildSpaceContainer(),
|
||||
if (isHovered)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
bottom: -5,
|
||||
child: PlusButtonWidget(
|
||||
offset: Offset.zero,
|
||||
onButtonTap: widget.onTap,
|
||||
onTap: widget.onTap,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -20,21 +20,19 @@ class SpaceCell extends StatelessWidget {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
width: 150,
|
||||
padding: const EdgeInsetsDirectional.only(end: 10),
|
||||
height: 70,
|
||||
decoration: _containerDecoration(),
|
||||
child: Row(
|
||||
spacing: 10,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildIconContainer(),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
name,
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
Text(
|
||||
name,
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -3,6 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/create_space_button.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||
|
||||
class SpaceManagementCommunityStructure extends StatelessWidget {
|
||||
@ -10,31 +13,59 @@ class SpaceManagementCommunityStructure extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectionBloc = context.watch<CommunitiesTreeSelectionBloc>().state;
|
||||
final selectedCommunity = selectionBloc.selectedCommunity;
|
||||
final selectedSpace = selectionBloc.selectedSpace;
|
||||
return BlocBuilder<CommunitiesTreeSelectionBloc, CommunitiesTreeSelectionState>(
|
||||
builder: (context, state) {
|
||||
final selectedCommunity = state.selectedCommunity;
|
||||
final selectedSpace = state.selectedSpace;
|
||||
|
||||
if (selectedCommunity == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const CommunityStructureHeader(),
|
||||
BlocBuilder<CommunitiesBloc, CommunitiesState>(
|
||||
builder: (context, state) {
|
||||
final community = state.communities.firstWhere(
|
||||
(element) => element.uuid == selectedCommunity.uuid,
|
||||
orElse: () => selectedCommunity,
|
||||
);
|
||||
return Visibility(
|
||||
visible: community.spaces.isNotEmpty,
|
||||
replacement: _buildEmptyWidget(community),
|
||||
child: _buildCanvas(community, selectedSpace),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCanvas(
|
||||
CommunityModel selectedCommunity,
|
||||
SpaceModel? selectedSpace,
|
||||
) {
|
||||
return Expanded(
|
||||
child: CommunityStructureCanvas(
|
||||
community: selectedCommunity,
|
||||
selectedSpace: selectedSpace,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyWidget(CommunityModel selectedCommunity) {
|
||||
const spacer = Spacer(flex: 6);
|
||||
return Visibility(
|
||||
visible: selectedCommunity!.spaces.isNotEmpty,
|
||||
replacement: Row(
|
||||
|
||||
return Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
spacer,
|
||||
Expanded(
|
||||
child: CreateSpaceButton(communityUuid: selectedCommunity.uuid),
|
||||
),
|
||||
spacer
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const CommunityStructureHeader(),
|
||||
Expanded(
|
||||
child: CommunityStructureCanvas(
|
||||
community: selectedCommunity,
|
||||
selectedSpace: selectedSpace,
|
||||
),
|
||||
),
|
||||
Expanded(child: CreateSpaceButton(community: selectedCommunity)),
|
||||
spacer,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -0,0 +1,63 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/create_space/domain/services/create_space_service.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
|
||||
final class RemoteCreateSpaceService implements CreateSpaceService {
|
||||
const RemoteCreateSpaceService(this._httpService);
|
||||
|
||||
final HTTPService _httpService;
|
||||
|
||||
static const _defaultErrorMessage = 'Failed to create space';
|
||||
|
||||
@override
|
||||
Future<SpaceModel> createSpace(CreateSpaceParam param) async {
|
||||
try {
|
||||
final path = await _makeUrl(param);
|
||||
final response = await _httpService.post(
|
||||
path: path,
|
||||
body: param.toJson(),
|
||||
expectedResponseModel: (data) {
|
||||
final response = data as Map<String, dynamic>;
|
||||
final isSuccess = response['success'] as bool;
|
||||
if (!isSuccess) {
|
||||
throw APIException(response['error'] as String);
|
||||
}
|
||||
|
||||
return SpaceModel.fromJson(response['data'] as Map<String, dynamic>);
|
||||
},
|
||||
);
|
||||
|
||||
return response;
|
||||
} on DioException catch (e) {
|
||||
final message = e.response?.data as Map<String, dynamic>?;
|
||||
final error = message?['error'] as Map<String, dynamic>?;
|
||||
final errorMessage = error?['error'] as String? ?? '';
|
||||
final formattedErrorMessage = [
|
||||
_defaultErrorMessage,
|
||||
errorMessage,
|
||||
].join(': ');
|
||||
throw APIException(formattedErrorMessage);
|
||||
} catch (e) {
|
||||
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
|
||||
throw APIException(formattedErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _makeUrl(CreateSpaceParam param) async {
|
||||
final projectUuid = await ProjectManager.getProjectUUID();
|
||||
if (projectUuid == null || projectUuid.isEmpty) {
|
||||
throw APIException('Project UUID is not set');
|
||||
}
|
||||
|
||||
final communityUuid = param.communityUuid;
|
||||
if (communityUuid.isEmpty) {
|
||||
throw APIException('Community UUID is not set');
|
||||
}
|
||||
|
||||
return '/projects/$projectUuid/communities/$communityUuid/spaces';
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||
|
||||
class CreateSpaceParam {
|
||||
final String communityUuid;
|
||||
final SpaceDetailsModel space;
|
||||
final String? parentUuid;
|
||||
|
||||
const CreateSpaceParam({
|
||||
required this.communityUuid,
|
||||
required this.space,
|
||||
required this.parentUuid,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'parentUuid': parentUuid,
|
||||
...space.toJson(),
|
||||
'x': 0,
|
||||
'y': 0,
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart';
|
||||
|
||||
abstract interface class CreateSpaceService {
|
||||
Future<SpaceModel> createSpace(CreateSpaceParam param);
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/create_space/domain/params/create_space_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/create_space/domain/services/create_space_service.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
|
||||
part 'create_space_event.dart';
|
||||
part 'create_space_state.dart';
|
||||
|
||||
class CreateSpaceBloc extends Bloc<CreateSpaceEvent, CreateSpaceState> {
|
||||
CreateSpaceBloc(
|
||||
this._createSpaceService,
|
||||
) : super(const CreateSpaceInitial()) {
|
||||
on<CreateSpace>(_onCreateSpace);
|
||||
}
|
||||
|
||||
final CreateSpaceService _createSpaceService;
|
||||
|
||||
Future<void> _onCreateSpace(
|
||||
CreateSpace event,
|
||||
Emitter<CreateSpaceState> emit,
|
||||
) async {
|
||||
emit(const CreateSpaceLoading());
|
||||
try {
|
||||
final result = await _createSpaceService.createSpace(event.param);
|
||||
emit(CreateSpaceSuccess(result));
|
||||
} on APIException catch (e) {
|
||||
emit(CreateSpaceFailure(e.message));
|
||||
} catch (e) {
|
||||
emit(CreateSpaceFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
part of 'create_space_bloc.dart';
|
||||
|
||||
sealed class CreateSpaceEvent extends Equatable {
|
||||
const CreateSpaceEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class CreateSpace extends CreateSpaceEvent {
|
||||
const CreateSpace(this.param);
|
||||
|
||||
final CreateSpaceParam param;
|
||||
|
||||
@override
|
||||
List<Object> get props => [param];
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user