mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-16 01:56:24 +00:00
Compare commits
24 Commits
Implement-
...
SP-1721-FE
Author | SHA1 | Date | |
---|---|---|---|
d65f9ceea9 | |||
f539b0ac8d | |||
5a3cf93748 | |||
e740652507 | |||
c60078c96a | |||
903c5dd29b | |||
df39fca050 | |||
f832c5d884 | |||
fa930571dc | |||
acefe7b355 | |||
b223194950 | |||
466f5b89c7 | |||
de5d8df01c | |||
5218641705 | |||
ab6a6851f2 | |||
beb33e37fa | |||
3bee17c574 | |||
f4b5c6fb52 | |||
086f3cedf8 | |||
035c03c6b2 | |||
cf1b34ee0a | |||
5663e2084e | |||
3cd0125310 | |||
e0980b324c |
@ -1,10 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_9795_9381)">
|
||||
<path d="M9.21875 13.5149V10.7805H6.48438C6.05286 10.7805 5.70312 10.4308 5.70312 9.99924C5.70312 9.56787 6.05286 9.21799 6.48438 9.21799H9.21875V6.48361C9.21875 6.05225 9.56848 5.70236 10 5.70236C10.4315 5.70236 10.7812 6.05225 10.7812 6.48361V9.21799H13.5156C13.9471 9.21799 14.2969 9.56787 14.2969 9.99924C14.2969 10.4308 13.9471 10.7805 13.5156 10.7805H10.7812V13.5149C10.7812 13.9464 10.4315 14.2961 10 14.2961C9.56848 14.2961 9.21875 13.9464 9.21875 13.5149ZM17.0711 2.92892C15.1823 1.04019 12.6711 0 10 0C7.32895 0 4.81766 1.04019 2.92892 2.92892C1.04019 4.81766 0 7.32895 0 10C0 12.6711 1.04019 15.1823 2.92892 17.0711C4.81766 18.9598 7.32895 20 10 20C11.8286 20 13.6179 19.5016 15.1743 18.5588C15.5434 18.3353 15.6613 17.8549 15.4378 17.486C15.2142 17.1169 14.7337 16.9989 14.3648 17.2224C13.0525 18.0173 11.5431 18.4375 10 18.4375C5.3476 18.4375 1.5625 14.6524 1.5625 10C1.5625 5.3476 5.3476 1.5625 10 1.5625C14.6524 1.5625 18.4375 5.3476 18.4375 10C18.4375 11.6637 17.9428 13.2829 17.0068 14.6831C16.767 15.0417 16.8634 15.5269 17.2221 15.7668C17.5807 16.0065 18.0659 15.91 18.3058 15.5515C19.4141 13.8936 20 11.9739 20 10C20 7.32895 18.9598 4.81766 17.0711 2.92892Z" fill="#023DFE" fill-opacity="0.7"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_9795_9381">
|
||||
<rect width="20" height="20" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.4 KiB |
@ -1,4 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 20C15.514 20 19.9998 15.514 19.9998 9.99995C19.9998 4.48604 15.514 0 10 0C4.48613 0 0.000183105 4.48604 0.000183105 9.99995C0.000183105 15.514 4.48613 20 10 20ZM10 1.36892C14.7591 1.36892 18.6309 5.24077 18.631 9.99995C18.631 14.7591 14.7592 18.631 10 18.6311C5.24095 18.631 1.36919 14.7591 1.36919 9.99986C1.36919 5.24086 5.24095 1.36892 10 1.36892Z" fill="#023DFE" fill-opacity="0.7"/>
|
||||
<path d="M8.65713 14.2828C8.92444 14.55 9.35784 14.5499 9.62505 14.2828C9.89245 14.0154 9.89245 13.5821 9.62496 13.3147L6.99481 10.6846L14.6112 10.6839C14.9892 10.6838 15.2956 10.3775 15.2956 9.99926C15.2955 9.62126 14.9891 9.31499 14.6111 9.31499L6.99444 9.31572L9.62523 6.68511C9.89254 6.41781 9.89254 5.98432 9.62523 5.7171C9.49154 5.5835 9.3164 5.5166 9.14118 5.5166C8.96605 5.5166 8.79092 5.5835 8.65722 5.71701L4.85811 9.51604C4.7297 9.64435 4.65761 9.81838 4.65761 9.99999C4.6577 10.1816 4.7298 10.3555 4.8582 10.4841L8.65713 14.2828Z" fill="#023DFE" fill-opacity="0.7"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,4 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.9999 0C4.48595 0 0 4.48586 0 9.99971C0 15.514 4.48595 20 9.9999 20C15.5138 20 19.9996 15.5139 19.9996 9.99971C19.9996 4.48586 15.5138 0 9.9999 0ZM9.9999 18.5665C5.27638 18.5665 1.43349 14.7234 1.43349 9.99971C1.43349 5.27628 5.27638 1.43349 9.9999 1.43349C14.7233 1.43349 18.5661 5.27628 18.5661 9.99971C18.5661 14.7234 14.7233 18.5665 9.9999 18.5665Z" fill="#D5D5D5"/>
|
||||
<path d="M15.1416 9.83211H10.4423V4.69526C10.4423 4.29943 10.1215 3.97852 9.72553 3.97852C9.3297 3.97852 9.00879 4.29943 9.00879 4.69526V10.5489C9.00879 10.9447 9.3297 11.2656 9.72553 11.2656H15.1416C15.5376 11.2656 15.8584 10.9447 15.8584 10.5489C15.8584 10.153 15.5375 9.83211 15.1416 9.83211Z" fill="#D5D5D5"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 799 B |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 18 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 |
@ -18,7 +18,7 @@ class RemoteBookableSpacesService implements BookableSystemService {
|
||||
}) async {
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
path: ApiEndpoints.bookableSpaces,
|
||||
path: ApiEndpoints.getBookableSpaces,
|
||||
queryParameters: {
|
||||
'page': param.page,
|
||||
'size': param.size,
|
||||
|
@ -12,13 +12,10 @@ class SvgTextButton extends StatelessWidget {
|
||||
final double borderRadius;
|
||||
final List<BoxShadow> boxShadow;
|
||||
final double svgSize;
|
||||
final double? fontSize;
|
||||
final FontWeight? fontWeight;
|
||||
|
||||
const SvgTextButton({
|
||||
super.key,
|
||||
required this.svgAsset,
|
||||
this.fontSize,
|
||||
this.fontWeight,
|
||||
required this.label,
|
||||
required this.onPressed,
|
||||
this.backgroundColor = ColorsManager.circleRolesBackground,
|
||||
|
@ -1,59 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/icon_text_button.dart';
|
||||
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class BookingPage extends StatelessWidget {
|
||||
final PageController pageController;
|
||||
const BookingPage({
|
||||
super.key,
|
||||
required this.pageController,
|
||||
});
|
||||
|
||||
@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),
|
||||
),
|
||||
),
|
||||
)),
|
||||
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: () {})
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
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/bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/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';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
class RemoteBookableSpacesService implements BookableSpacesService {
|
||||
final HTTPService _httpService;
|
||||
RemoteBookableSpacesService(this._httpService);
|
||||
static const _defaultErrorMessage = 'Failed to load Bookable Spaces';
|
||||
@override
|
||||
Future<PaginatedDataModel<BookableSpacemodel>> load(
|
||||
BookableSpacesParams param) async {
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
path: ApiEndpoints.bookableSpaces,
|
||||
queryParameters: {
|
||||
'configured': true,
|
||||
'page': param.currentPage,
|
||||
},
|
||||
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>?;
|
||||
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,74 +0,0 @@
|
||||
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/service/non_bookable_spaces_service.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';
|
||||
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 RemoteNonBookableSpaces implements NonBookableSpacesService {
|
||||
final HTTPService _httpService;
|
||||
RemoteNonBookableSpaces(this._httpService);
|
||||
static const _defaultErrorMessage = 'Failed to load Spaces';
|
||||
|
||||
@override
|
||||
Future<PaginatedDataModel<BookableSpacemodel>> load(
|
||||
NonBookableSpacesParams params) 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,
|
||||
);
|
||||
},
|
||||
);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@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,40 +0,0 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_config.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/domain/service/update_bookable_space_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 RemoteUpdateBookableSpaceService implements UpdateBookableSpaceService {
|
||||
final HTTPService _httpService;
|
||||
RemoteUpdateBookableSpaceService(this._httpService);
|
||||
static const _defaultErrorMessage = 'Failed to load Bookable Spaces';
|
||||
@override
|
||||
Future<BookableSpaceConfig> update(
|
||||
UpdateBookableSpaceParam updateParam) async {
|
||||
try {
|
||||
final response = await _httpService.put(
|
||||
path: '${ApiEndpoints.bookableSpaces}/${updateParam.spaceUuid}',
|
||||
body: updateParam.toJson(),
|
||||
expectedResponseModel: (json) {
|
||||
return BookableSpaceConfig.fromJson(
|
||||
json['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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BookableSpaceConfig {
|
||||
String configUuid;
|
||||
List<String> bookableDays;
|
||||
TimeOfDay? bookingStartTime;
|
||||
TimeOfDay? bookingEndTime;
|
||||
int cost;
|
||||
bool availability;
|
||||
BookableSpaceConfig({
|
||||
required this.configUuid,
|
||||
required this.availability,
|
||||
required this.bookableDays,
|
||||
this.bookingEndTime,
|
||||
this.bookingStartTime,
|
||||
required this.cost,
|
||||
});
|
||||
factory BookableSpaceConfig.zero() => BookableSpaceConfig(
|
||||
configUuid: '',
|
||||
bookableDays: [],
|
||||
availability: false,
|
||||
cost: -1,
|
||||
);
|
||||
factory BookableSpaceConfig.fromJson(Map<String, dynamic> json) =>
|
||||
BookableSpaceConfig(
|
||||
configUuid: json['uuid'] as String,
|
||||
bookableDays: (json['daysAvailable'] as List).cast<String>(),
|
||||
availability: (json['active'] as bool?) ?? false,
|
||||
bookingStartTime: parseTimeOfDay(json['startTime'] as String),
|
||||
bookingEndTime: parseTimeOfDay(json['endTime'] as String),
|
||||
cost: json['points'] as int,
|
||||
);
|
||||
|
||||
static TimeOfDay parseTimeOfDay(String timeString) {
|
||||
final parts = timeString.split(':');
|
||||
final hour = int.parse(parts[0]);
|
||||
final minute = int.parse(parts[1]);
|
||||
return TimeOfDay(hour: hour, minute: minute);
|
||||
}
|
||||
|
||||
bool get isValid =>
|
||||
bookableDays.isNotEmpty &&
|
||||
cost >= 0 &&
|
||||
bookingStartTime != null &&
|
||||
bookingEndTime != null;
|
||||
|
||||
BookableSpaceConfig copyWith({
|
||||
List<String>? bookableDays,
|
||||
TimeOfDay? bookingStartTime,
|
||||
TimeOfDay? bookingEndTime,
|
||||
int? cost,
|
||||
bool? availability,
|
||||
}) {
|
||||
return BookableSpaceConfig(
|
||||
configUuid: configUuid,
|
||||
availability: availability ?? this.availability,
|
||||
bookableDays: bookableDays ?? this.bookableDays,
|
||||
cost: cost ?? this.cost,
|
||||
bookingEndTime: bookingEndTime ?? this.bookingEndTime,
|
||||
bookingStartTime: bookingStartTime ?? this.bookingStartTime,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
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;
|
||||
|
||||
BookableSpacemodel({
|
||||
required this.spaceUuid,
|
||||
required this.spaceName,
|
||||
this.spaceConfig,
|
||||
required this.spaceVirtualAddress,
|
||||
});
|
||||
factory BookableSpacemodel.zero() => BookableSpacemodel(
|
||||
spaceUuid: '',
|
||||
spaceName: '',
|
||||
spaceVirtualAddress: '',
|
||||
);
|
||||
factory BookableSpacemodel.fromJson(Map<String, dynamic> json) =>
|
||||
BookableSpacemodel(
|
||||
spaceUuid: json['uuid'] as String,
|
||||
spaceName: json['spaceName'] as String,
|
||||
spaceConfig: json['bookableConfig'] == null
|
||||
? BookableSpaceConfig.zero()
|
||||
: BookableSpaceConfig.fromJson(
|
||||
json['bookableConfig'] as Map<String, dynamic>),
|
||||
spaceVirtualAddress: json['virtualLocation'] as String,
|
||||
);
|
||||
|
||||
static List<BookableSpacemodel> fromJsonList(List<dynamic> jsonList) =>
|
||||
jsonList
|
||||
.map(
|
||||
(e) => BookableSpacemodel.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList();
|
||||
|
||||
bool get isValid =>
|
||||
spaceUuid.isNotEmpty &&
|
||||
spaceName.isNotEmpty &&
|
||||
spaceVirtualAddress.isNotEmpty &&
|
||||
spaceConfig != null &&
|
||||
spaceConfig!.isValid;
|
||||
|
||||
BookableSpacemodel copyWith({
|
||||
String? spaceUuid,
|
||||
String? spaceName,
|
||||
BookableSpaceConfig? spaceConfig,
|
||||
String? spaceVirtualAddress,
|
||||
}) {
|
||||
return BookableSpacemodel(
|
||||
spaceUuid: spaceUuid ?? this.spaceUuid,
|
||||
spaceName: spaceName ?? this.spaceName,
|
||||
spaceConfig: spaceConfig ?? this.spaceConfig,
|
||||
spaceVirtualAddress: spaceVirtualAddress ?? this.spaceVirtualAddress,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
class BookableSpacesParams {
|
||||
int currentPage;
|
||||
BookableSpacesParams({
|
||||
required this.currentPage,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'page': currentPage,
|
||||
};
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
class NonBookableSpacesParams {
|
||||
int currentPage;
|
||||
String? searchedWords;
|
||||
NonBookableSpacesParams({
|
||||
required this.currentPage,
|
||||
this.searchedWords,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'page': currentPage,
|
||||
};
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
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 SendBookableSpacesToApiParams {
|
||||
List<String> spaceUuids;
|
||||
List<String> daysAvailable;
|
||||
String startTime;
|
||||
String endTime;
|
||||
int points;
|
||||
SendBookableSpacesToApiParams({
|
||||
required this.spaceUuids,
|
||||
required this.daysAvailable,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
required this.points,
|
||||
});
|
||||
|
||||
static SendBookableSpacesToApiParams fromBookableSpacesModel(
|
||||
List<BookableSpacemodel> bookableSpaces) {
|
||||
return SendBookableSpacesToApiParams(
|
||||
spaceUuids: bookableSpaces.map((space) => space.spaceUuid).toList(),
|
||||
daysAvailable: bookableSpaces
|
||||
.expand((space) => space.spaceConfig!.bookableDays)
|
||||
.toSet()
|
||||
.toList(),
|
||||
startTime: formatTimeOfDayTo24HourString(
|
||||
bookableSpaces.first.spaceConfig!.bookingStartTime!),
|
||||
endTime: formatTimeOfDayTo24HourString(
|
||||
bookableSpaces.first.spaceConfig!.bookingEndTime!),
|
||||
points: bookableSpaces.first.spaceConfig!.cost,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'spaceUuids': spaceUuids,
|
||||
'daysAvailable': daysAvailable,
|
||||
'startTime': startTime,
|
||||
'endTime': endTime,
|
||||
'points': points
|
||||
};
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
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;
|
||||
UpdateBookableSpaceParam({
|
||||
required this.spaceUuid,
|
||||
this.bookingStartTime,
|
||||
this.bookingEndTime,
|
||||
this.bookableDays,
|
||||
this.availability,
|
||||
this.cost,
|
||||
});
|
||||
factory UpdateBookableSpaceParam.fromBookableModel(
|
||||
BookableSpacemodel bookableSpace) {
|
||||
return UpdateBookableSpaceParam(
|
||||
spaceUuid: bookableSpace.spaceUuid,
|
||||
availability: bookableSpace.spaceConfig!.availability,
|
||||
bookableDays: bookableSpace.spaceConfig!.bookableDays,
|
||||
cost: bookableSpace.spaceConfig!.cost,
|
||||
bookingStartTime: formatTimeOfDayTo24HourString(
|
||||
bookableSpace.spaceConfig!.bookingStartTime!),
|
||||
bookingEndTime: formatTimeOfDayTo24HourString(
|
||||
bookableSpace.spaceConfig!.bookingEndTime!),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
if (bookableDays != null) 'daysAvailable': bookableDays,
|
||||
if (bookingStartTime != null) 'startTime': bookingStartTime,
|
||||
if (bookingEndTime != null) 'endTime': bookingEndTime,
|
||||
if (cost != null) 'points': cost,
|
||||
if (availability != null) 'active': availability,
|
||||
};
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
|
||||
abstract class BookableSpacesService {
|
||||
Future<PaginatedDataModel<BookableSpacemodel>> load(
|
||||
BookableSpacesParams param);
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
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);
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_config.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/update_bookable_space_param.dart';
|
||||
|
||||
abstract class UpdateBookableSpaceService {
|
||||
Future<BookableSpaceConfig> update(UpdateBookableSpaceParam updateParam);
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_config.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/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';
|
||||
|
||||
part 'bookable_spaces_event.dart';
|
||||
part 'bookable_spaces_state.dart';
|
||||
|
||||
class BookableSpacesBloc
|
||||
extends Bloc<BookableSpacesEvent, BookableSpacesState> {
|
||||
final BookableSpacesService bookableSpacesService;
|
||||
BookableSpacesBloc(this.bookableSpacesService)
|
||||
: super(BookableSpacesInitial()) {
|
||||
on<LoadBookableSpacesEvent>(_onLoadBookableSpaces);
|
||||
on<InsertUpdatedSpaceEvent>(_onInsertUpdatedSpaceEven);
|
||||
}
|
||||
|
||||
Future<void> _onLoadBookableSpaces(
|
||||
LoadBookableSpacesEvent event, Emitter<BookableSpacesState> emit) async {
|
||||
emit(BookableSpacesLoading());
|
||||
try {
|
||||
final bookableSpaces = await bookableSpacesService.load(event.params);
|
||||
emit(BookableSpacesLoaded(bookableSpacesList: bookableSpaces));
|
||||
} on APIException catch (e) {
|
||||
emit(BookableSpacesError(error: e.message));
|
||||
} catch (e) {
|
||||
emit(
|
||||
BookableSpacesError(error: e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onInsertUpdatedSpaceEven(
|
||||
InsertUpdatedSpaceEvent event, Emitter<BookableSpacesState> emit) {
|
||||
emit(InsertingUpdatedSpaceState());
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
emit(BookableSpacesLoaded(bookableSpacesList: event.bookableSpaces));
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
part of 'bookable_spaces_bloc.dart';
|
||||
|
||||
sealed class BookableSpacesEvent extends Equatable {
|
||||
const BookableSpacesEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoadBookableSpacesEvent extends BookableSpacesEvent {
|
||||
final BookableSpacesParams params;
|
||||
const LoadBookableSpacesEvent(this.params);
|
||||
}
|
||||
|
||||
class InsertUpdatedSpaceEvent extends BookableSpacesEvent {
|
||||
final PaginatedDataModel<BookableSpacemodel> bookableSpaces;
|
||||
final BookableSpacemodel bookableSpace;
|
||||
final BookableSpaceConfig updatedBookableSpaceConfig;
|
||||
const InsertUpdatedSpaceEvent({
|
||||
required this.bookableSpaces,
|
||||
required this.bookableSpace,
|
||||
required this.updatedBookableSpaceConfig,
|
||||
});
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
part of 'bookable_spaces_bloc.dart';
|
||||
|
||||
sealed class BookableSpacesState extends Equatable {
|
||||
const BookableSpacesState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class BookableSpacesInitial extends BookableSpacesState {}
|
||||
|
||||
final class BookableSpacesLoading extends BookableSpacesState {}
|
||||
|
||||
final class BookableSpacesLoaded extends BookableSpacesState {
|
||||
final PaginatedDataModel<BookableSpacemodel> bookableSpacesList;
|
||||
const BookableSpacesLoaded({
|
||||
required this.bookableSpacesList,
|
||||
});
|
||||
}
|
||||
|
||||
final class BookableSpacesError extends BookableSpacesState {
|
||||
final String error;
|
||||
const BookableSpacesError({
|
||||
required this.error,
|
||||
});
|
||||
}
|
||||
|
||||
class InsertingUpdatedSpaceState extends BookableSpacesState {}
|
@ -1,18 +0,0 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
part 'toggle_points_switch_state.dart';
|
||||
|
||||
class TogglePointsSwitchCubit extends Cubit<TogglePointsSwitchState> {
|
||||
TogglePointsSwitchCubit() : super(TogglePointsSwitchInitial());
|
||||
bool switchValue = true;
|
||||
void activateSwitch() {
|
||||
switchValue = true;
|
||||
emit(ActivatePointsSwitch());
|
||||
}
|
||||
|
||||
void unActivateSwitch() {
|
||||
switchValue = false;
|
||||
emit(UnActivatePointsSwitch());
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
part of 'toggle_points_switch_cubit.dart';
|
||||
|
||||
sealed class TogglePointsSwitchState extends Equatable {
|
||||
const TogglePointsSwitchState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class TogglePointsSwitchInitial extends TogglePointsSwitchState {}
|
||||
|
||||
class ActivatePointsSwitch extends TogglePointsSwitchState {}
|
||||
|
||||
class UnActivatePointsSwitch extends TogglePointsSwitchState {}
|
@ -1,150 +0,0 @@
|
||||
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/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';
|
||||
|
||||
part 'non_bookaable_spaces_event.dart';
|
||||
part 'non_bookaable_spaces_state.dart';
|
||||
|
||||
class NonBookableSpacesBloc
|
||||
extends Bloc<NonBookableSpacesEvent, NonBookableSpacesState> {
|
||||
NonBookableSpacesService nonBookableSpacesService;
|
||||
List<BookableSpacemodel> selectedBookableSpaces = [];
|
||||
NonBookableSpacesBloc(this.nonBookableSpacesService)
|
||||
: super(NonBookableSpacesInitial()) {
|
||||
on<CallInitStateEvent>(_onCallInitStateEvent);
|
||||
on<LoadUnBookableSpacesEvent>(_onLoadUnBookableSpacesEvent);
|
||||
on<AddToBookableSpaceEvent>(_onAddToBookableSpaceEvent);
|
||||
on<RemoveFromBookableSpaceEvent>(_onRemoveFromBookableSpaceEvent);
|
||||
on<SendBookableSpacesToApi>(_onSendBookableSpacesToApi);
|
||||
on<CheckConfigurValidityEvent>(_onCheckConfigurValidityEvent);
|
||||
on<EditModeSelected>(_onEditModeSelected);
|
||||
}
|
||||
|
||||
TimeOfDay? get endTime =>
|
||||
selectedBookableSpaces.first.spaceConfig!.bookingEndTime;
|
||||
|
||||
TimeOfDay? get startTime =>
|
||||
selectedBookableSpaces.first.spaceConfig!.bookingStartTime;
|
||||
|
||||
void _onCallInitStateEvent(
|
||||
CallInitStateEvent event, Emitter<NonBookableSpacesState> emit) {
|
||||
emit(NonBookableSpacesInitial());
|
||||
}
|
||||
|
||||
Future<void> _onLoadUnBookableSpacesEvent(LoadUnBookableSpacesEvent event,
|
||||
Emitter<NonBookableSpacesState> emit) async {
|
||||
if (state is NonBookableSpacesLoaded) {
|
||||
final currState = state as NonBookableSpacesLoaded;
|
||||
try {
|
||||
emit(NonBookableSpacesLoading(
|
||||
lastNonBookableSpaces: currState.nonBookableSpaces));
|
||||
|
||||
final nonBookableSpacesList = await nonBookableSpacesService.load(
|
||||
event.nonBookableSpacesParams,
|
||||
);
|
||||
nonBookableSpacesList.data.addAll(currState.nonBookableSpaces.data);
|
||||
|
||||
emit(
|
||||
NonBookableSpacesLoaded(
|
||||
nonBookableSpaces: nonBookableSpacesList,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
NonBookableSpacesError(e.toString()),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
emit(const NonBookableSpacesLoading());
|
||||
final nonBookableSpacesList = await nonBookableSpacesService.load(
|
||||
event.nonBookableSpacesParams,
|
||||
);
|
||||
emit(
|
||||
NonBookableSpacesLoaded(nonBookableSpaces: nonBookableSpacesList),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
NonBookableSpacesError(e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _onAddToBookableSpaceEvent(
|
||||
AddToBookableSpaceEvent event,
|
||||
Emitter<NonBookableSpacesState> emit,
|
||||
) {
|
||||
if (state is NonBookableSpacesLoaded) {
|
||||
final currentState = state as NonBookableSpacesLoaded;
|
||||
emit(AddNonBookableSpaceIntoBookableState());
|
||||
final updatedSelectedSpaces =
|
||||
List<BookableSpacemodel>.from(currentState.selectedBookableSpaces)
|
||||
..add(event.nonBookableSpace);
|
||||
|
||||
selectedBookableSpaces.add(event.nonBookableSpace);
|
||||
|
||||
emit(
|
||||
NonBookableSpacesLoaded(
|
||||
nonBookableSpaces: currentState.nonBookableSpaces,
|
||||
selectedBookableSpaces: updatedSelectedSpaces,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onRemoveFromBookableSpaceEvent(RemoveFromBookableSpaceEvent event,
|
||||
Emitter<NonBookableSpacesState> emit) {
|
||||
if (state is NonBookableSpacesLoaded) {
|
||||
final currentState = state as NonBookableSpacesLoaded;
|
||||
emit(RemoveBookableSpaceIntoNonBookableState());
|
||||
if (currentState.selectedBookableSpaces.isNotEmpty) {
|
||||
currentState.selectedBookableSpaces.remove(event.bookableSpace);
|
||||
}
|
||||
selectedBookableSpaces.remove(event.bookableSpace);
|
||||
emit(
|
||||
NonBookableSpacesLoaded(
|
||||
nonBookableSpaces: currentState.nonBookableSpaces,
|
||||
selectedBookableSpaces: currentState.selectedBookableSpaces,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onSendBookableSpacesToApi(SendBookableSpacesToApi event,
|
||||
Emitter<NonBookableSpacesState> emit) async {
|
||||
emit(const NonBookableSpacesLoading());
|
||||
try {
|
||||
await nonBookableSpacesService.sendBookableSpacesToApi(
|
||||
SendBookableSpacesToApiParams.fromBookableSpacesModel(
|
||||
selectedBookableSpaces,
|
||||
),
|
||||
);
|
||||
add(CallInitStateEvent());
|
||||
} catch (e) {
|
||||
emit(
|
||||
NonBookableSpacesError(e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onCheckConfigurValidityEvent(
|
||||
CheckConfigurValidityEvent event, Emitter<NonBookableSpacesState> emit) {
|
||||
if (selectedBookableSpaces.first.spaceConfig!.isValid) {
|
||||
emit(ValidSaveButtonState());
|
||||
} else {
|
||||
emit(UnValidSaveButtonState());
|
||||
}
|
||||
}
|
||||
|
||||
void _onEditModeSelected(
|
||||
EditModeSelected event, Emitter<NonBookableSpacesState> emit) {
|
||||
selectedBookableSpaces.clear();
|
||||
selectedBookableSpaces.add(event.editingBookableSpace);
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
part of 'non_bookaable_spaces_bloc.dart';
|
||||
|
||||
sealed class NonBookableSpacesEvent extends Equatable {
|
||||
const NonBookableSpacesEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class CallInitStateEvent extends NonBookableSpacesEvent {}
|
||||
|
||||
class LoadUnBookableSpacesEvent extends NonBookableSpacesEvent {
|
||||
final NonBookableSpacesParams nonBookableSpacesParams;
|
||||
const LoadUnBookableSpacesEvent({
|
||||
required this.nonBookableSpacesParams,
|
||||
});
|
||||
}
|
||||
|
||||
class AddToBookableSpaceEvent extends NonBookableSpacesEvent {
|
||||
final BookableSpacemodel nonBookableSpace;
|
||||
const AddToBookableSpaceEvent({
|
||||
required this.nonBookableSpace,
|
||||
});
|
||||
}
|
||||
|
||||
class RemoveFromBookableSpaceEvent extends NonBookableSpacesEvent {
|
||||
final BookableSpacemodel bookableSpace;
|
||||
const RemoveFromBookableSpaceEvent({
|
||||
required this.bookableSpace,
|
||||
});
|
||||
}
|
||||
|
||||
class SendBookableSpacesToApi extends NonBookableSpacesEvent {}
|
||||
|
||||
class CheckConfigurValidityEvent extends NonBookableSpacesEvent {}
|
||||
|
||||
class EditModeSelected extends NonBookableSpacesEvent {
|
||||
final BookableSpacemodel editingBookableSpace;
|
||||
const EditModeSelected({
|
||||
required this.editingBookableSpace,
|
||||
});
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
part of 'non_bookaable_spaces_bloc.dart';
|
||||
|
||||
sealed class NonBookableSpacesState extends Equatable {
|
||||
const NonBookableSpacesState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class NonBookableSpacesInitial extends NonBookableSpacesState {}
|
||||
|
||||
class NonBookableSpacesLoading extends NonBookableSpacesState {
|
||||
final PaginatedDataModel<BookableSpacemodel>? lastNonBookableSpaces;
|
||||
const NonBookableSpacesLoading({
|
||||
this.lastNonBookableSpaces,
|
||||
});
|
||||
}
|
||||
|
||||
class NonBookableSpacesLoaded extends NonBookableSpacesState {
|
||||
final PaginatedDataModel<BookableSpacemodel> nonBookableSpaces;
|
||||
final List<BookableSpacemodel> selectedBookableSpaces;
|
||||
const NonBookableSpacesLoaded({
|
||||
required this.nonBookableSpaces,
|
||||
this.selectedBookableSpaces = const [],
|
||||
});
|
||||
}
|
||||
|
||||
class NonBookableSpacesError extends NonBookableSpacesState {
|
||||
final String error;
|
||||
const NonBookableSpacesError(this.error);
|
||||
}
|
||||
|
||||
class AddNonBookableSpaceIntoBookableState extends NonBookableSpacesState {}
|
||||
|
||||
class RemoveBookableSpaceIntoNonBookableState extends NonBookableSpacesState {}
|
||||
|
||||
class ValidSaveButtonState extends NonBookableSpacesState {}
|
||||
|
||||
class UnValidSaveButtonState extends NonBookableSpacesState {}
|
@ -1,22 +0,0 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
part 'steps_state.dart';
|
||||
|
||||
class StepsCubit extends Cubit<StepsState> {
|
||||
StepsCubit() : super(StepsInitial());
|
||||
|
||||
void initDialogValue() {
|
||||
emit(StepOneState());
|
||||
}
|
||||
|
||||
void editValueInit() {
|
||||
emit(StepTwoState());
|
||||
}
|
||||
|
||||
void goToNextStep() {
|
||||
if (state is StepOneState) {
|
||||
emit(StepTwoState());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
part of 'steps_cubit.dart';
|
||||
|
||||
sealed class StepsState extends Equatable {
|
||||
const StepsState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class StepsInitial extends StepsState {}
|
||||
|
||||
final class StepOneState extends StepsState {}
|
||||
|
||||
final class StepTwoState extends StepsState {}
|
||||
|
@ -1,36 +0,0 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_config.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/domain/service/update_bookable_space_service.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
|
||||
part 'update_bookable_spaces_event.dart';
|
||||
part 'update_bookable_spaces_state.dart';
|
||||
|
||||
class UpdateBookableSpacesBloc
|
||||
extends Bloc<UpdateBookableSpaceEvent, UpdateBookableSpacesState> {
|
||||
final UpdateBookableSpaceService updateBookableSpaceService;
|
||||
UpdateBookableSpacesBloc(this.updateBookableSpaceService)
|
||||
: super(UpdateBookableSpacesInitial()) {
|
||||
on<UpdateBookableSpace>(_onUpdateBookableSpace);
|
||||
}
|
||||
|
||||
Future<void> _onUpdateBookableSpace(UpdateBookableSpace event,
|
||||
Emitter<UpdateBookableSpacesState> emit) async {
|
||||
emit(UpdateBookableSpaceLoading(event.updatedParams.spaceUuid));
|
||||
try {
|
||||
final updatedSpace =
|
||||
await updateBookableSpaceService.update(event.updatedParams);
|
||||
|
||||
emit(UpdateBookableSpaceSuccess(bookableSpaceConfig: updatedSpace));
|
||||
event.onSuccess?.call();
|
||||
} on APIException catch (e) {
|
||||
emit(UpdateBookableSpaceFailure(error: e.message));
|
||||
} catch (e) {
|
||||
emit(
|
||||
UpdateBookableSpaceFailure(error: e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
part of 'update_bookable_spaces_bloc.dart';
|
||||
|
||||
sealed class UpdateBookableSpaceEvent extends Equatable {
|
||||
const UpdateBookableSpaceEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class UpdateBookableSpace extends UpdateBookableSpaceEvent {
|
||||
final void Function()? onSuccess;
|
||||
final UpdateBookableSpaceParam updatedParams;
|
||||
const UpdateBookableSpace({
|
||||
required this.updatedParams,
|
||||
this.onSuccess,
|
||||
});
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
part of 'update_bookable_spaces_bloc.dart';
|
||||
|
||||
sealed class UpdateBookableSpacesState extends Equatable {
|
||||
const UpdateBookableSpacesState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class UpdateBookableSpacesInitial extends UpdateBookableSpacesState {}
|
||||
|
||||
final class UpdateBookableSpaceLoading extends UpdateBookableSpacesState {
|
||||
final String updatingSpaceUuid;
|
||||
|
||||
const UpdateBookableSpaceLoading(this.updatingSpaceUuid);
|
||||
}
|
||||
|
||||
final class UpdateBookableSpaceSuccess extends UpdateBookableSpacesState {
|
||||
final BookableSpaceConfig bookableSpaceConfig;
|
||||
const UpdateBookableSpaceSuccess({
|
||||
required this.bookableSpaceConfig,
|
||||
});
|
||||
}
|
||||
|
||||
final class UpdateBookableSpaceFailure extends UpdateBookableSpacesState {
|
||||
final String error;
|
||||
const UpdateBookableSpaceFailure({
|
||||
required this.error,
|
||||
});
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/data/remote_bookable_spaces_service.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/data/remote_update_bookable_space_service.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/bookable_spaces_bloc/bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/update_bookable_spaces/update_bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/main_manage_bookable_widgets/bottom_pagination_part_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/main_manage_bookable_widgets/table_part_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/main_manage_bookable_widgets/top_part_widget.dart';
|
||||
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
|
||||
class ManageBookableSpacesPage extends StatefulWidget {
|
||||
final PageController pageController;
|
||||
const ManageBookableSpacesPage({
|
||||
super.key,
|
||||
required this.pageController,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ManageBookableSpacesPage> createState() =>
|
||||
_ManageBookableSpacesPageState();
|
||||
}
|
||||
|
||||
class _ManageBookableSpacesPageState extends State<ManageBookableSpacesPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (context) => BookableSpacesBloc(
|
||||
RemoteBookableSpacesService(HTTPService()),
|
||||
)..add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => UpdateBookableSpacesBloc(
|
||||
RemoteUpdateBookableSpaceService(HTTPService()),
|
||||
),
|
||||
)
|
||||
],
|
||||
child: ManageBookableSpacesWidget(
|
||||
pageController: widget.pageController,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ManageBookableSpacesWidget extends StatelessWidget {
|
||||
final PageController pageController;
|
||||
|
||||
const ManageBookableSpacesWidget({
|
||||
super.key,
|
||||
required this.pageController,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 10, child: TopPartWidget(pageController: pageController)),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
const Expanded(
|
||||
flex: 85,
|
||||
child: TablePartWidget(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
const Expanded(
|
||||
flex: 5,
|
||||
child: BottomPaginationPartWidget(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/data/remote_non_bookable_spaces.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/non_bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/steps_cubit/cubit/steps_cubit.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/details_steps_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/next_first_step_button.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/save_second_step_button.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/stepper_part_widget.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class SetupBookableSpacesDialog extends StatelessWidget {
|
||||
final TextEditingController pointsController = TextEditingController();
|
||||
final BookableSpacemodel? editingBookableSpace;
|
||||
SetupBookableSpacesDialog({
|
||||
super.key,
|
||||
this.editingBookableSpace,
|
||||
});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<StepsCubit>(
|
||||
create: editingBookableSpace == null
|
||||
? (context) => StepsCubit()..initDialogValue()
|
||||
: (context) => StepsCubit()..editValueInit(),
|
||||
),
|
||||
BlocProvider<NonBookableSpacesBloc>(
|
||||
create: editingBookableSpace == null
|
||||
? (context) => NonBookableSpacesBloc(
|
||||
RemoteNonBookableSpaces(HTTPService()),
|
||||
)..add(
|
||||
LoadUnBookableSpacesEvent(
|
||||
nonBookableSpacesParams:
|
||||
NonBookableSpacesParams(currentPage: 1),
|
||||
),
|
||||
)
|
||||
: (context) => NonBookableSpacesBloc(
|
||||
RemoteNonBookableSpaces(HTTPService()),
|
||||
)..add(
|
||||
EditModeSelected(
|
||||
editingBookableSpace: editingBookableSpace!),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: AlertDialog(
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Center(
|
||||
child: Text(
|
||||
'Set Up a Bookable Spaces',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: ColorsManager.dialogBlueTitle,
|
||||
fontSize: 15,
|
||||
),
|
||||
),
|
||||
),
|
||||
content: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Divider(),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Expanded(
|
||||
flex: 3,
|
||||
child: StepperPartWidget(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 588,
|
||||
child: VerticalDivider(
|
||||
thickness: 0.5,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 7,
|
||||
child: DetailsStepsWidget(
|
||||
pointsController: pointsController,
|
||||
editingBookableSpace: editingBookableSpace,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Builder(builder: (context) {
|
||||
final stepsState = context.watch<StepsCubit>().state;
|
||||
final nonBookableBloc = context.watch<NonBookableSpacesBloc>();
|
||||
final selectedSpaces = nonBookableBloc.selectedBookableSpaces;
|
||||
return stepsState is StepOneState
|
||||
? NextFirstStepButton(selectedSpaces: selectedSpaces)
|
||||
: SaveSecondStepButton(
|
||||
selectedSpaces: selectedSpaces,
|
||||
pointsController: pointsController,
|
||||
isEditingMode: editingBookableSpace != null,
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/time_picker_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/string_utils.dart';
|
||||
|
||||
class BookingPeriodWidget extends StatelessWidget {
|
||||
final BookableSpacemodel? editingBookableSpace;
|
||||
const BookingPeriodWidget({
|
||||
super.key,
|
||||
this.editingBookableSpace,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'* ',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(color: Colors.red),
|
||||
),
|
||||
const Text('Booking Period'),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
width: 300,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: ColorsManager.graysColor,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
TimePickerWidget(
|
||||
title: editingBookableSpace == null
|
||||
? 'Start Time'
|
||||
: editingBookableSpace!.spaceConfig!.bookingStartTime!
|
||||
.format(context),
|
||||
onTimePicked: (timePicked) {
|
||||
if (timePicked == null) {
|
||||
return;
|
||||
}
|
||||
final nonBookableBloc = context.read<NonBookableSpacesBloc>();
|
||||
|
||||
if (nonBookableBloc.endTime != null &&
|
||||
isEndTimeAfterStartTime(
|
||||
timePicked, nonBookableBloc.endTime!)) {
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||
content:
|
||||
Text("You can't choose start Time Before End time"),
|
||||
duration: Duration(seconds: 2),
|
||||
backgroundColor: ColorsManager.red,
|
||||
));
|
||||
throw Exception();
|
||||
} else {
|
||||
nonBookableBloc.selectedBookableSpaces.forEach(
|
||||
(e) => e.spaceConfig!.bookingStartTime = timePicked,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
const Icon(
|
||||
Icons.arrow_right_alt,
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
TimePickerWidget(
|
||||
title: editingBookableSpace == null
|
||||
? 'End Time'
|
||||
: editingBookableSpace!.spaceConfig!.bookingEndTime!
|
||||
.format(context),
|
||||
onTimePicked: (timePicked) {
|
||||
if (timePicked == null) {
|
||||
return;
|
||||
}
|
||||
final nonBookableBloc = context.read<NonBookableSpacesBloc>();
|
||||
if (nonBookableBloc.startTime != null &&
|
||||
isEndTimeAfterStartTime(
|
||||
nonBookableBloc.startTime!, timePicked)) {
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||
content:
|
||||
Text("You can't choose End Time After Start time"),
|
||||
duration: Duration(seconds: 2),
|
||||
backgroundColor: ColorsManager.red,
|
||||
));
|
||||
throw Exception();
|
||||
} else {
|
||||
nonBookableBloc.selectedBookableSpaces.forEach(
|
||||
(e) => e.spaceConfig!.bookingEndTime = timePicked,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
Container(
|
||||
width: 50,
|
||||
height: 32,
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(10),
|
||||
bottomLeft: Radius.circular(10),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: SvgPicture.asset(
|
||||
Assets.clockIcon,
|
||||
height: 15,
|
||||
color: ColorsManager.blackColor.withValues(alpha: 0.4),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/bookable_spaces_bloc/bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class ButtonsDividerBottomDialogWidget extends StatelessWidget {
|
||||
final String title;
|
||||
final void Function()? onNextPressed;
|
||||
final void Function() onCancelPressed;
|
||||
const ButtonsDividerBottomDialogWidget({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.onNextPressed,
|
||||
required this.onCancelPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const Divider(
|
||||
thickness: 0.5,
|
||||
height: 1,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(26),
|
||||
),
|
||||
onTap: onCancelPressed,
|
||||
child: Container(
|
||||
height: 40,
|
||||
alignment: Alignment.center,
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
right: BorderSide(
|
||||
color: ColorsManager.grayBorder,
|
||||
),
|
||||
),
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(26),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Cancel',
|
||||
style: TextStyle(color: ColorsManager.blackColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child:
|
||||
BlocConsumer<NonBookableSpacesBloc, NonBookableSpacesState>(
|
||||
listener: (context, nonBookableState) {
|
||||
if (nonBookableState is NonBookableSpacesInitial) {
|
||||
context.pop();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Operation Done Successfully',
|
||||
style: TextStyle(color: ColorsManager.activeGreen),
|
||||
),
|
||||
duration: Duration(seconds: 2),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: 1),
|
||||
),
|
||||
);
|
||||
} else if (nonBookableState is NonBookableSpacesError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
nonBookableState.error,
|
||||
style:
|
||||
const TextStyle(color: ColorsManager.activeGreen),
|
||||
),
|
||||
duration: const Duration(seconds: 2),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, nonBookableState) {
|
||||
return TextButton(
|
||||
onPressed: onNextPressed,
|
||||
child: Text(
|
||||
title,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
|
||||
class CheckBoxSpaceWidget extends StatelessWidget {
|
||||
final BookableSpacemodel nonBookableSpace;
|
||||
final List<BookableSpacemodel> selectedSpaces;
|
||||
|
||||
const CheckBoxSpaceWidget({
|
||||
super.key,
|
||||
required this.nonBookableSpace,
|
||||
required this.selectedSpaces,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isChecked = selectedSpaces.any(
|
||||
(element) => element.spaceUuid == nonBookableSpace.spaceUuid,
|
||||
);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: isChecked,
|
||||
onChanged: (value) {
|
||||
final bloc = context.read<NonBookableSpacesBloc>();
|
||||
if (value ?? false) {
|
||||
bloc.add(
|
||||
AddToBookableSpaceEvent(
|
||||
nonBookableSpace: nonBookableSpace,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
bloc.add(
|
||||
RemoveFromBookableSpaceEvent(
|
||||
bookableSpace: nonBookableSpace,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
Expanded(child: Text(nonBookableSpace.spaceName)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class ColumnTitleWidget extends StatelessWidget {
|
||||
final bool isFirst;
|
||||
final bool isLast;
|
||||
final String title;
|
||||
const ColumnTitleWidget({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.isFirst,
|
||||
required this.isLast,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.graysColor,
|
||||
borderRadius: isFirst
|
||||
? const BorderRadius.only(
|
||||
topLeft: Radius.circular(12),
|
||||
)
|
||||
: isLast
|
||||
? const BorderRadius.only(
|
||||
topRight: Radius.circular(12),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
color: ColorsManager.grayColor,
|
||||
fontSize: 12,
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
import 'package:data_table_2/data_table_2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/column_title_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class CustomDataTable<T> extends StatelessWidget {
|
||||
final List<String> columnsTitles;
|
||||
final List<DataCell> Function(T item) cellsWidgets;
|
||||
final List<T> items;
|
||||
|
||||
const CustomDataTable({
|
||||
super.key,
|
||||
required this.items,
|
||||
required this.cellsWidgets,
|
||||
required this.columnsTitles,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DataTable2(
|
||||
dividerThickness: 0.5,
|
||||
columnSpacing: 2,
|
||||
horizontalMargin: 0,
|
||||
empty: SvgPicture.asset(Assets.emptyDataTable),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.circleRolesBackground,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: ColorsManager.textGray,
|
||||
blurRadius: 12,
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
columns: columnsTitles.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final title = entry.value;
|
||||
|
||||
return DataColumn(
|
||||
label: ColumnTitleWidget(
|
||||
title: title,
|
||||
isFirst: index == 0,
|
||||
isLast: index == columnsTitles.length - 1,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
rows: items.map((item) {
|
||||
return DataRow(cells: cellsWidgets(item));
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/steps_cubit/cubit/steps_cubit.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/space_step_part_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/step_two_details_widget.dart';
|
||||
|
||||
class DetailsStepsWidget extends StatelessWidget {
|
||||
final TextEditingController pointsController;
|
||||
final BookableSpacemodel? editingBookableSpace;
|
||||
const DetailsStepsWidget({
|
||||
super.key,
|
||||
required this.pointsController,
|
||||
this.editingBookableSpace,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 20),
|
||||
child: BlocBuilder<StepsCubit, StepsState>(
|
||||
builder: (context, state) {
|
||||
if (state is StepOneState) {
|
||||
return const SpacesStepDetailsWidget();
|
||||
} else if (state is StepTwoState) {
|
||||
return StepTwoDetailsWidget(
|
||||
pointsController: pointsController,
|
||||
editingBookableSpace:editingBookableSpace
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,158 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/bookable_spaces_bloc/bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class BottomPaginationPartWidget extends StatelessWidget {
|
||||
const BottomPaginationPartWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<BookableSpacesBloc, BookableSpacesState>(
|
||||
builder: (context, state) {
|
||||
if (state is BookableSpacesLoaded) {
|
||||
final totalPages = state.bookableSpacesList.totalPages;
|
||||
final currentPage = state.bookableSpacesList.page;
|
||||
|
||||
List<Widget> paginationItems = [];
|
||||
|
||||
// « Two pages back
|
||||
if (currentPage > 2) {
|
||||
paginationItems.add(
|
||||
_buildArrowButton(
|
||||
label: '«',
|
||||
onTap: () {
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: currentPage - 2),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// < One page back
|
||||
if (currentPage > 1) {
|
||||
paginationItems.add(
|
||||
_buildArrowButton(
|
||||
label: '<',
|
||||
onTap: () {
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: currentPage - 1),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Page numbers
|
||||
for (int i = 1; i <= totalPages; i++) {
|
||||
paginationItems.add(
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (i != currentPage) {
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: i),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: 30,
|
||||
height: 30,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: i == currentPage
|
||||
? ColorsManager.dialogBlueTitle
|
||||
: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'$i',
|
||||
style: TextStyle(
|
||||
color: i == currentPage ? Colors.white : Colors.black,
|
||||
fontWeight: i == currentPage
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// > One page forward
|
||||
if (currentPage < totalPages) {
|
||||
paginationItems.add(
|
||||
_buildArrowButton(
|
||||
label: '>',
|
||||
onTap: () {
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: currentPage + 1),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// » Two pages forward
|
||||
if (currentPage + 1 < totalPages) {
|
||||
paginationItems.add(
|
||||
_buildArrowButton(
|
||||
label: '»',
|
||||
onTap: () {
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: currentPage + 2),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: paginationItems,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildArrowButton(
|
||||
{required String label, required VoidCallback onTap}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,201 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/data/remote_update_bookable_space_service.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/update_bookable_space_param.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/bookable_spaces_bloc/bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/update_bookable_spaces/update_bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/screens/setup_bookable_spaces_dialog.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/custom_data_table.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class TablePartWidget extends StatelessWidget {
|
||||
const TablePartWidget({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<BookableSpacesBloc, BookableSpacesState>(
|
||||
builder: (context, state) {
|
||||
if (state is BookableSpacesLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is BookableSpacesError) {
|
||||
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
Text(state.error),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => context
|
||||
.read<BookableSpacesBloc>()
|
||||
.add(LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: 1),
|
||||
)),
|
||||
child: const Text('try Again'))
|
||||
]);
|
||||
} else if (state is BookableSpacesLoaded) {
|
||||
return CustomDataTable<BookableSpacemodel>(
|
||||
items: state.bookableSpacesList.data,
|
||||
cellsWidgets: (space) => [
|
||||
DataCell(
|
||||
Padding(
|
||||
padding: const EdgeInsetsGeometry.only(left: 10),
|
||||
child: Text(
|
||||
space.spaceName,
|
||||
style: const TextStyle(fontSize: 11),
|
||||
)),
|
||||
),
|
||||
DataCell(Padding(
|
||||
padding: const EdgeInsetsGeometry.only(left: 10),
|
||||
child: Text(
|
||||
space.spaceVirtualAddress,
|
||||
style: const TextStyle(fontSize: 11),
|
||||
))),
|
||||
DataCell(Container(
|
||||
padding: const EdgeInsetsGeometry.only(left: 10),
|
||||
width: 200,
|
||||
child: Wrap(
|
||||
spacing: 4,
|
||||
children: space.spaceConfig!.bookableDays
|
||||
.map((day) => Text(
|
||||
day,
|
||||
style: const TextStyle(fontSize: 11),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
)),
|
||||
DataCell(
|
||||
Padding(
|
||||
padding: const EdgeInsetsGeometry.only(left: 10),
|
||||
child: Text(
|
||||
space.spaceConfig!.bookingStartTime!.format(context),
|
||||
style: const TextStyle(fontSize: 11),
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Padding(
|
||||
padding: const EdgeInsetsGeometry.only(left: 10),
|
||||
child: Text(
|
||||
space.spaceConfig!.bookingEndTime!.format(context),
|
||||
style: const TextStyle(fontSize: 11),
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell(Padding(
|
||||
padding: const EdgeInsetsGeometry.only(left: 10),
|
||||
child: Text(
|
||||
'${space.spaceConfig!.cost} Points',
|
||||
style: const TextStyle(fontSize: 11),
|
||||
))),
|
||||
DataCell(Center(
|
||||
child: Transform.scale(
|
||||
scale: 0.7,
|
||||
child: BlocConsumer<UpdateBookableSpacesBloc,
|
||||
UpdateBookableSpacesState>(
|
||||
listener: (context, updateState) {
|
||||
if (updateState is UpdateBookableSpaceSuccess) {
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
InsertUpdatedSpaceEvent(
|
||||
bookableSpaces: state.bookableSpacesList,
|
||||
bookableSpace: space,
|
||||
updatedBookableSpaceConfig:
|
||||
updateState.bookableSpaceConfig,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, updateState) {
|
||||
final isLoading =
|
||||
updateState is UpdateBookableSpaceLoading &&
|
||||
updateState.updatingSpaceUuid == space.spaceUuid;
|
||||
if (isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return Switch(
|
||||
trackOutlineColor:
|
||||
WidgetStateProperty.resolveWith<Color>(
|
||||
(Set<WidgetState> states) {
|
||||
return ColorsManager.whiteColors;
|
||||
}),
|
||||
value: space.spaceConfig!.availability,
|
||||
activeTrackColor: ColorsManager.blueColor,
|
||||
inactiveTrackColor: ColorsManager.grayBorder,
|
||||
thumbColor: WidgetStateProperty.resolveWith<Color>(
|
||||
(Set<WidgetState> states) {
|
||||
return ColorsManager.whiteColors;
|
||||
}),
|
||||
onChanged: (value) {
|
||||
context.read<UpdateBookableSpacesBloc>().add(
|
||||
UpdateBookableSpace(
|
||||
updatedParams: UpdateBookableSpaceParam(
|
||||
spaceUuid: space.spaceUuid,
|
||||
availability: value,
|
||||
)),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
)),
|
||||
DataCell(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,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
fixedSize: const Size(50, 30),
|
||||
elevation: 1,
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
Assets.settings,
|
||||
height: 15,
|
||||
color: ColorsManager.blue1,
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
columnsTitles: const [
|
||||
'Space',
|
||||
'Space Virtual Address',
|
||||
'Bookable Days',
|
||||
'Booking Start Time',
|
||||
'Booking End Time',
|
||||
'Cost',
|
||||
'Availability',
|
||||
'Settings',
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/icon_text_button.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/bookable_spaces_bloc/bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/screens/setup_bookable_spaces_dialog.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class TopPartWidget extends StatelessWidget {
|
||||
const TopPartWidget({
|
||||
super.key,
|
||||
required this.pageController,
|
||||
});
|
||||
|
||||
final PageController pageController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsGeometry.symmetric(vertical: 5),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
Assets.backButtonIcon,
|
||||
height: 15,
|
||||
),
|
||||
onPressed: () {
|
||||
pageController.jumpToPage(1);
|
||||
}),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Text(
|
||||
'Manage Bookable Spaces',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: ColorsManager.vividBlue.withValues(
|
||||
alpha: 0.7,
|
||||
),
|
||||
fontWeight: FontWeight.w700),
|
||||
)
|
||||
],
|
||||
),
|
||||
SvgTextButton(
|
||||
svgSize: 15,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
svgAsset: Assets.addButtonIcon,
|
||||
label: 'Set Up a Bookable Spaces',
|
||||
onPressed: () {
|
||||
final bloc = context.read<BookableSpacesBloc>();
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: bloc,
|
||||
child: SetupBookableSpacesDialog(),
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/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<NonBookableSpacesBloc>()
|
||||
.add(CheckConfigurValidityEvent());
|
||||
},
|
||||
onCancelPressed: () => context.pop(),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/cubit/toggle_points_switch_cubit.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/search_unbookable_spaces_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class PointsPartWidget extends StatefulWidget {
|
||||
final BookableSpacemodel? editingBookableSpace;
|
||||
const PointsPartWidget({
|
||||
super.key,
|
||||
required this.pointsController,
|
||||
this.editingBookableSpace,
|
||||
});
|
||||
|
||||
final TextEditingController pointsController;
|
||||
|
||||
@override
|
||||
State<PointsPartWidget> createState() => _PointsPartWidgetState();
|
||||
}
|
||||
|
||||
class _PointsPartWidgetState extends State<PointsPartWidget> {
|
||||
@override
|
||||
void initState() {
|
||||
if (widget.editingBookableSpace != null) {
|
||||
widget.pointsController.text =
|
||||
widget.editingBookableSpace!.spaceConfig!.cost.toString();
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<TogglePointsSwitchCubit, TogglePointsSwitchState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
if (state is ActivatePointsSwitch)
|
||||
Text(
|
||||
'* ',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(color: Colors.red),
|
||||
)
|
||||
else
|
||||
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,
|
||||
onChanged: (value) {
|
||||
if (value) {
|
||||
context
|
||||
.read<TogglePointsSwitchCubit>()
|
||||
.activateSwitch();
|
||||
context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.selectedBookableSpaces
|
||||
.forEach(
|
||||
(e) => e.spaceConfig!.cost = -1,
|
||||
);
|
||||
context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.add(CheckConfigurValidityEvent());
|
||||
} else {
|
||||
context
|
||||
.read<TogglePointsSwitchCubit>()
|
||||
.unActivateSwitch();
|
||||
widget.pointsController.clear();
|
||||
context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.selectedBookableSpaces
|
||||
.forEach(
|
||||
(e) => e.spaceConfig!.cost = 0,
|
||||
);
|
||||
context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.add(CheckConfigurValidityEvent());
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
if (state is ActivatePointsSwitch)
|
||||
SearchUnbookableSpacesWidget(
|
||||
title: 'Ex: 0',
|
||||
height: 40,
|
||||
onChanged: (p0) {
|
||||
context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.selectedBookableSpaces
|
||||
.forEach(
|
||||
(e) => e.spaceConfig!.cost = int.parse(
|
||||
widget.pointsController.text.isEmpty
|
||||
? '0'
|
||||
: widget.pointsController.text,
|
||||
),
|
||||
);
|
||||
context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.add(CheckConfigurValidityEvent());
|
||||
},
|
||||
controller: widget.pointsController,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
suffix: const SizedBox(),
|
||||
)
|
||||
else
|
||||
const SizedBox(),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/update_bookable_space_param.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/update_bookable_spaces/update_bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/buttons_divider_bottom_dialog_widget.dart';
|
||||
|
||||
class SaveSecondStepButton extends StatelessWidget {
|
||||
final List<BookableSpacemodel> selectedSpaces;
|
||||
final TextEditingController pointsController;
|
||||
final bool isEditingMode;
|
||||
|
||||
const SaveSecondStepButton({
|
||||
super.key,
|
||||
required this.selectedSpaces,
|
||||
required this.pointsController,
|
||||
required this.isEditingMode,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<NonBookableSpacesBloc, NonBookableSpacesState>(
|
||||
builder: (context, state) {
|
||||
return ButtonsDividerBottomDialogWidget(
|
||||
title: 'Save',
|
||||
onNextPressed: state is UnValidSaveButtonState
|
||||
? null
|
||||
: () {
|
||||
if (selectedSpaces.any(
|
||||
(element) => element.isValid,
|
||||
)) {
|
||||
isEditingMode
|
||||
? callEditLogic(context)
|
||||
: context.read<NonBookableSpacesBloc>().add(
|
||||
SendBookableSpacesToApi(),
|
||||
);
|
||||
}
|
||||
},
|
||||
onCancelPressed: () => context.pop(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void callEditLogic(BuildContext context) {
|
||||
context.read<UpdateBookableSpacesBloc>().add(
|
||||
UpdateBookableSpace(
|
||||
onSuccess: () =>
|
||||
context.read<NonBookableSpacesBloc>().add(CallInitStateEvent()),
|
||||
updatedParams: UpdateBookableSpaceParam.fromBookableModel(
|
||||
context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.selectedBookableSpaces
|
||||
.first,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class SearchUnbookableSpacesWidget extends StatelessWidget {
|
||||
final String title;
|
||||
final Widget? suffix;
|
||||
final double? height;
|
||||
final double? width;
|
||||
final TextEditingController? controller;
|
||||
final List<TextInputFormatter>? inputFormatters;
|
||||
final void Function(String)? onChanged;
|
||||
const SearchUnbookableSpacesWidget({
|
||||
required this.title,
|
||||
this.controller,
|
||||
this.onChanged,
|
||||
this.suffix,
|
||||
this.height,
|
||||
this.width,
|
||||
this.inputFormatters,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: width ?? 480,
|
||||
height: height ?? 30,
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x26000000),
|
||||
offset: Offset(0, 4),
|
||||
blurRadius: 5,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
inputFormatters: inputFormatters,
|
||||
onChanged: onChanged,
|
||||
decoration: InputDecoration(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
|
||||
hintText: title,
|
||||
hintStyle: const TextStyle(color: Colors.grey),
|
||||
border: InputBorder.none,
|
||||
suffixIcon:
|
||||
suffix ?? const Icon(Icons.search, size: 20, color: Colors.grey),
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,181 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/non_bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/search_unbookable_spaces_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/unbookable_list_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class SpacesStepDetailsWidget extends StatefulWidget {
|
||||
const SpacesStepDetailsWidget({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SpacesStepDetailsWidget> createState() =>
|
||||
_SpacesStepDetailsWidgetState();
|
||||
}
|
||||
|
||||
class _SpacesStepDetailsWidgetState extends State<SpacesStepDetailsWidget> {
|
||||
Timer? _debounce;
|
||||
ScrollController scrollController = ScrollController();
|
||||
int currentPage = 1;
|
||||
String? currentSearchTerm;
|
||||
bool isLoadingMore = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
scrollController.addListener(() {
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent - 100) {
|
||||
final state = context.read<NonBookableSpacesBloc>().state;
|
||||
if (state is NonBookableSpacesLoaded &&
|
||||
state.nonBookableSpaces.hasNext &&
|
||||
!isLoadingMore) {
|
||||
isLoadingMore = true;
|
||||
currentPage++;
|
||||
context.read<NonBookableSpacesBloc>().add(
|
||||
LoadUnBookableSpacesEvent(
|
||||
nonBookableSpacesParams: NonBookableSpacesParams(
|
||||
currentPage: currentPage,
|
||||
searchedWords: currentSearchTerm,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Select Space',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Container(
|
||||
width: 450,
|
||||
height: 480,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x40000000),
|
||||
offset: Offset.zero,
|
||||
blurRadius: 5,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 520,
|
||||
height: 70,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 15, horizontal: 20),
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFF8F8F8),
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: SearchUnbookableSpacesWidget(
|
||||
title: 'Search',
|
||||
onChanged: (p0) {
|
||||
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
||||
_debounce = Timer(const Duration(milliseconds: 500), () {
|
||||
currentSearchTerm = p0;
|
||||
currentPage = 1;
|
||||
context.read<NonBookableSpacesBloc>().add(
|
||||
LoadUnBookableSpacesEvent(
|
||||
nonBookableSpacesParams: NonBookableSpacesParams(
|
||||
currentPage: currentPage,
|
||||
searchedWords: currentSearchTerm,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child:
|
||||
BlocConsumer<NonBookableSpacesBloc, NonBookableSpacesState>(
|
||||
listener: (context, state) {
|
||||
if (state is NonBookableSpacesLoaded) {
|
||||
isLoadingMore = false;
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state is NonBookableSpacesError) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(state.error),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.read<NonBookableSpacesBloc>().add(
|
||||
LoadUnBookableSpacesEvent(
|
||||
nonBookableSpacesParams:
|
||||
NonBookableSpacesParams(
|
||||
currentPage: currentPage,
|
||||
searchedWords: currentSearchTerm,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Try Again'))
|
||||
],
|
||||
);
|
||||
} else if (state is NonBookableSpacesLoading) {
|
||||
if (state.lastNonBookableSpaces == null) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
} else {
|
||||
return UnbookableListWidget(
|
||||
scrollController: scrollController,
|
||||
nonBookableSpaces: state.lastNonBookableSpaces!,
|
||||
);
|
||||
}
|
||||
} else if (state is NonBookableSpacesLoaded) {
|
||||
return UnbookableListWidget(
|
||||
scrollController: scrollController,
|
||||
nonBookableSpaces: state.nonBookableSpaces,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/cubit/toggle_points_switch_cubit.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/booking_period_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/points_part_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/week_checkbox_title_widget.dart';
|
||||
|
||||
class StepTwoDetailsWidget extends StatelessWidget {
|
||||
final TextEditingController pointsController;
|
||||
final BookableSpacemodel? editingBookableSpace;
|
||||
const StepTwoDetailsWidget({
|
||||
super.key,
|
||||
required this.pointsController,
|
||||
this.editingBookableSpace,
|
||||
});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 450,
|
||||
height: 480,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
WeekDaysCheckboxRow(
|
||||
editingBookableSpace: editingBookableSpace,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
BookingPeriodWidget(
|
||||
editingBookableSpace: editingBookableSpace,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
BlocProvider(
|
||||
create: editingBookableSpace == null
|
||||
? (context) => TogglePointsSwitchCubit()..activateSwitch()
|
||||
: editingBookableSpace!.spaceConfig!.cost == 0
|
||||
? (context) => TogglePointsSwitchCubit()..unActivateSwitch()
|
||||
: (context) => TogglePointsSwitchCubit()..activateSwitch(),
|
||||
child: PointsPartWidget(
|
||||
pointsController: pointsController,
|
||||
editingBookableSpace: editingBookableSpace),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/steps_cubit/cubit/steps_cubit.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class StepperPartWidget extends StatelessWidget {
|
||||
const StepperPartWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(20),
|
||||
padding: const EdgeInsetsGeometry.only(left: 20),
|
||||
child: BlocBuilder<StepsCubit, StepsState>(
|
||||
builder: (context, state) {
|
||||
if (state is StepOneState) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
const CircleTitleStepperWidget(
|
||||
title: 'Space',
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.only(left: 3),
|
||||
alignment: Alignment.centerLeft,
|
||||
height: 50,
|
||||
child: const VerticalDivider(
|
||||
width: 8,
|
||||
)),
|
||||
const CircleTitleStepperWidget(
|
||||
title: 'Settings',
|
||||
titleColor: ColorsManager.softGray,
|
||||
circleColor: ColorsManager.whiteColors,
|
||||
borderColor: ColorsManager.textGray,
|
||||
)
|
||||
],
|
||||
);
|
||||
} else if (state is StepTwoState) {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
const CircleTitleStepperWidget(
|
||||
title: 'Space',
|
||||
titleColor: ColorsManager.softGray,
|
||||
cicleIcon: Icon(
|
||||
Icons.check,
|
||||
color: ColorsManager.whiteColors,
|
||||
size: 12,
|
||||
),
|
||||
circleColor: ColorsManager.trueIconGreen,
|
||||
radius: 15,
|
||||
borderColor: ColorsManager.trueIconGreen,
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.only(left: 3),
|
||||
alignment: Alignment.centerLeft,
|
||||
height: 50,
|
||||
child: const VerticalDivider(
|
||||
width: 8,
|
||||
)),
|
||||
const CircleTitleStepperWidget(
|
||||
title: 'Settings',
|
||||
)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CircleTitleStepperWidget extends StatelessWidget {
|
||||
final double? radius;
|
||||
final Widget? cicleIcon;
|
||||
final Color? circleColor;
|
||||
final Color? borderColor;
|
||||
final Color? titleColor;
|
||||
final String title;
|
||||
const CircleTitleStepperWidget({
|
||||
super.key,
|
||||
required this.title,
|
||||
this.circleColor,
|
||||
this.borderColor,
|
||||
this.cicleIcon,
|
||||
this.titleColor,
|
||||
this.radius,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
width: radius ?? 15,
|
||||
height: radius ?? 15,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: circleColor ?? ColorsManager.blue1,
|
||||
border: Border.all(color: borderColor ?? ColorsManager.blue1)),
|
||||
child: cicleIcon,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: titleColor ?? ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class TimePickerWidget extends StatefulWidget {
|
||||
final String title;
|
||||
const TimePickerWidget({
|
||||
super.key,
|
||||
required this.onTimePicked,
|
||||
required this.title,
|
||||
});
|
||||
|
||||
final void Function(TimeOfDay? timePicked) onTimePicked;
|
||||
@override
|
||||
State<TimePickerWidget> createState() => _TimePickerWidgetState();
|
||||
}
|
||||
|
||||
class _TimePickerWidgetState extends State<TimePickerWidget> {
|
||||
TimeOfDay? timePicked;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
onTap: () async {
|
||||
final tempTime = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: TimeOfDay.now(),
|
||||
builder: (context, child) {
|
||||
return Theme(
|
||||
data: ThemeData.light().copyWith(
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: ColorsManager.primaryColor,
|
||||
onSurface: Colors.black,
|
||||
),
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
widget.onTimePicked(tempTime);
|
||||
timePicked = tempTime;
|
||||
context.read<NonBookableSpacesBloc>().add(CheckConfigurValidityEvent());
|
||||
setState(() {});
|
||||
},
|
||||
child: Container(
|
||||
width: 100,
|
||||
height: 32,
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(10),
|
||||
bottomRight: Radius.circular(10),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text(
|
||||
timePicked == null ? widget.title : timePicked!.format(context),
|
||||
style: TextStyle(
|
||||
color: ColorsManager.blackColor.withValues(alpha: 0.4),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/check_box_space_widget.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
|
||||
class UnbookableListWidget extends StatelessWidget {
|
||||
final PaginatedDataModel<BookableSpacemodel> nonBookableSpaces;
|
||||
const UnbookableListWidget({
|
||||
super.key,
|
||||
required this.scrollController,
|
||||
required this.nonBookableSpaces,
|
||||
});
|
||||
|
||||
final ScrollController scrollController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 490,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(
|
||||
bottom: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.only(top: 10, left: 20, bottom: 5),
|
||||
child: ListView.separated(
|
||||
separatorBuilder: (context, index) => const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
controller: scrollController,
|
||||
itemCount: nonBookableSpaces.data.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (index < nonBookableSpaces.data.length) {
|
||||
return CheckBoxSpaceWidget(
|
||||
nonBookableSpace: nonBookableSpaces.data[index],
|
||||
selectedSpaces:
|
||||
context.read<NonBookableSpacesBloc>().selectedBookableSpaces,
|
||||
);
|
||||
} else {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 10),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
|
||||
class WeekDaysCheckboxRow extends StatefulWidget {
|
||||
final BookableSpacemodel? editingBookableSpace;
|
||||
const WeekDaysCheckboxRow({
|
||||
super.key,
|
||||
this.editingBookableSpace,
|
||||
});
|
||||
|
||||
@override
|
||||
State<WeekDaysCheckboxRow> createState() => _WeekDaysCheckboxRowState();
|
||||
}
|
||||
|
||||
class _WeekDaysCheckboxRowState extends State<WeekDaysCheckboxRow> {
|
||||
final Map<String, bool> _daysChecked = {
|
||||
'Mon': false,
|
||||
'Tue': false,
|
||||
'Wed': false,
|
||||
'Thu': false,
|
||||
'Fri': false,
|
||||
'Sat': false,
|
||||
'Sun': false,
|
||||
};
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
final existingDays =
|
||||
widget.editingBookableSpace?.spaceConfig?.bookableDays ?? [];
|
||||
|
||||
for (var day in _daysChecked.keys) {
|
||||
if (existingDays.contains(day)) {
|
||||
_daysChecked[day] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _daysChecked.entries.map((entry) {
|
||||
return Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Checkbox(
|
||||
value: entry.value,
|
||||
onChanged: (newValue) {
|
||||
setState(() {
|
||||
_daysChecked[entry.key] = newValue ?? false;
|
||||
final selectedDays = _daysChecked.entries
|
||||
.where((e) => e.value)
|
||||
.map((e) => e.key)
|
||||
.toList();
|
||||
|
||||
for (var space in context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.selectedBookableSpaces) {
|
||||
space.spaceConfig!.bookableDays = selectedDays;
|
||||
}
|
||||
});
|
||||
|
||||
context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.add(CheckConfigurValidityEvent());
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
entry.key,
|
||||
style: const TextStyle(fontSize: 10),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
@ -2,9 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/view/booking_page.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/screens/manage_bookable_spaces_screen.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/booking_page.dart' hide BookingPage;
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/booking_page.dart';
|
||||
import 'package:syncrow_web/pages/access_management/view/access_overview_content.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
@ -73,14 +71,9 @@ class _AccessManagementPageState extends State<AccessManagementPage>
|
||||
scaffoldBody: PageView(
|
||||
controller: _pageController,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
const AccessOverviewContent(),
|
||||
BookingPage(
|
||||
pageController: _pageController,
|
||||
),
|
||||
ManageBookableSpacesPage(
|
||||
pageController: _pageController,
|
||||
),
|
||||
children: const [
|
||||
AccessOverviewContent(),
|
||||
BookingPage(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -132,8 +132,6 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
child: SingleChildScrollView(
|
||||
controller: _horizontalScrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics:
|
||||
widget.isEmpty ? const NeverScrollableScrollPhysics() : null,
|
||||
child: SizedBox(
|
||||
width: _totalTableWidth,
|
||||
child: Column(
|
||||
@ -166,6 +164,7 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Expanded(
|
||||
child: widget.isEmpty
|
||||
? _buildEmptyState()
|
||||
@ -266,7 +265,7 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: widget.size.height * 0.2),
|
||||
SizedBox(height: widget.size.height * 0.5),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -46,15 +46,15 @@ class DeviceManagementBloc
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
|
||||
if (spaceBloc.state.selectedCommunities.isEmpty) {
|
||||
devices = await DevicesManagementApi().fetchDevices(
|
||||
projectUuid,
|
||||
);
|
||||
devices = await DevicesManagementApi().fetchDevices('', '', projectUuid);
|
||||
} else {
|
||||
for (var community in spaceBloc.state.selectedCommunities) {
|
||||
for (final community in spaceBloc.state.selectedCommunities) {
|
||||
final spacesList =
|
||||
spaceBloc.state.selectedCommunityAndSpaces[community] ?? [];
|
||||
devices.addAll(await DevicesManagementApi()
|
||||
.fetchDevices(projectUuid, spacesId: spacesList));
|
||||
for (final space in spacesList) {
|
||||
devices.addAll(await DevicesManagementApi()
|
||||
.fetchDevices(community, space, projectUuid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,12 +24,12 @@ class DeviceManagementPage extends StatefulWidget with HelperResponsiveLayout {
|
||||
}
|
||||
|
||||
class _DeviceManagementPageState extends State<DeviceManagementPage> {
|
||||
@override
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
context.read<SpaceTreeBloc>().add(InitialEvent());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
@ -90,7 +90,7 @@ class _DeviceManagementPageState extends State<DeviceManagementPage> {
|
||||
const TriggerSwitchTabsEvent(isRoutineTab: true));
|
||||
},
|
||||
child: Text(
|
||||
'Workflow Automation',
|
||||
'Routines',
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
color: state.routineTab
|
||||
? ColorsManager.whiteColors
|
||||
|
@ -29,9 +29,7 @@ class CountdownModeButtons extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
elevation: 2.5,
|
||||
height: 40,
|
||||
borderRadius: 8,
|
||||
onPressed: () => Navigator.pop(context),
|
||||
backgroundColor: ColorsManager.boxColor,
|
||||
child: Text('Cancel', style: context.textTheme.bodyMedium),
|
||||
@ -41,8 +39,6 @@ class CountdownModeButtons extends StatelessWidget {
|
||||
Expanded(
|
||||
child: isActive
|
||||
? DefaultButton(
|
||||
elevation: 2.5,
|
||||
borderRadius: 8,
|
||||
height: 40,
|
||||
onPressed: () {
|
||||
context.read<ScheduleBloc>().add(
|
||||
@ -53,12 +49,10 @@ class CountdownModeButtons extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
},
|
||||
backgroundColor: ColorsManager.red100,
|
||||
backgroundColor: Colors.red,
|
||||
child: const Text('Stop'),
|
||||
)
|
||||
: DefaultButton(
|
||||
elevation: 2.5,
|
||||
borderRadius: 8,
|
||||
height: 40,
|
||||
onPressed: () {
|
||||
context.read<ScheduleBloc>().add(
|
||||
@ -69,7 +63,7 @@ class CountdownModeButtons extends StatelessWidget {
|
||||
countDownCode: countDownCode),
|
||||
);
|
||||
},
|
||||
backgroundColor: ColorsManager.primaryColorWithOpacity,
|
||||
backgroundColor: ColorsManager.primaryColor,
|
||||
child: const Text('Save'),
|
||||
),
|
||||
),
|
||||
|
@ -226,7 +226,6 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
||||
index.toString().padLeft(2, '0'),
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: isActive ? ColorsManager.grayColor : Colors.black,
|
||||
),
|
||||
),
|
||||
@ -241,8 +240,7 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
||||
label,
|
||||
style: const TextStyle(
|
||||
color: ColorsManager.grayColor,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -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),
|
||||
@ -76,8 +77,7 @@ class BuildScheduleView extends StatelessWidget {
|
||||
category: category,
|
||||
time: '',
|
||||
function: Status(
|
||||
code: code.toString(),
|
||||
value: true),
|
||||
code: code.toString(), value: null),
|
||||
days: [],
|
||||
),
|
||||
isEdit: false,
|
||||
|
@ -13,9 +13,9 @@ class ScheduleHeader extends StatelessWidget {
|
||||
Text(
|
||||
'Scheduling',
|
||||
style: TextStyle(
|
||||
color: ColorsManager.primaryColorWithOpacity,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 30,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 22,
|
||||
color: ColorsManager.dialogBlueTitle,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
|
@ -27,7 +27,7 @@ class ScheduleManagementUI extends StatelessWidget {
|
||||
width: 170,
|
||||
height: 40,
|
||||
child: DefaultButton(
|
||||
borderColor: ColorsManager.grayColor.withOpacity(0.5),
|
||||
borderColor: ColorsManager.boxColor,
|
||||
padding: 2,
|
||||
backgroundColor: ColorsManager.graysColor,
|
||||
borderRadius: 15,
|
||||
|
@ -19,8 +19,6 @@ class ScheduleModeButtons extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
elevation: 2.5,
|
||||
borderRadius: 8,
|
||||
height: 40,
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
@ -35,11 +33,9 @@ class ScheduleModeButtons extends StatelessWidget {
|
||||
const SizedBox(width: 20),
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
elevation: 2.5,
|
||||
borderRadius: 8,
|
||||
height: 40,
|
||||
onPressed: onSave,
|
||||
backgroundColor: ColorsManager.primaryColorWithOpacity,
|
||||
backgroundColor: ColorsManager.primaryColor,
|
||||
child: const Text('Save'),
|
||||
),
|
||||
),
|
||||
|
@ -35,12 +35,12 @@ class ScheduleModeSelector extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildRadioTile(
|
||||
context, 'Countdown', ScheduleModes.countdown, currentMode),
|
||||
_buildRadioTile(
|
||||
context, 'Schedule', ScheduleModes.schedule, currentMode),
|
||||
const Spacer(flex: 1),
|
||||
// _buildRadioTile(
|
||||
// context, 'Circulate', ScheduleModes.circulate, currentMode),
|
||||
// _buildRadioTile(
|
||||
@ -65,7 +65,6 @@ class ScheduleModeSelector extends StatelessWidget {
|
||||
style: context.textTheme.bodySmall!.copyWith(
|
||||
fontSize: 13,
|
||||
color: ColorsManager.blackColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
leading: Radio<ScheduleModes>(
|
||||
|
@ -1,8 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_buttons.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class ScheduleDialogHelper {
|
||||
static const List<String> allDays = [
|
||||
@ -58,9 +56,8 @@ class ScheduleDialogHelper {
|
||||
Text(
|
||||
isEdit ? 'Edit Schedule' : 'Add Schedule',
|
||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||
color: ColorsManager.primaryColorWithOpacity,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 30,
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(),
|
||||
@ -72,9 +69,9 @@ class ScheduleDialogHelper {
|
||||
height: 40,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: ColorsManager.boxColor,
|
||||
backgroundColor: Colors.grey[200],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
@ -113,27 +110,39 @@ class ScheduleDialogHelper {
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
ScheduleModeButtons(
|
||||
onSave: () {
|
||||
dynamic temp;
|
||||
if (deviceType == 'CUR_2') {
|
||||
temp = functionOn! ? 'open' : 'close';
|
||||
} else {
|
||||
temp = functionOn;
|
||||
}
|
||||
final entry = ScheduleEntry(
|
||||
category: schedule?.category ?? 'switch_1',
|
||||
time: _formatTimeOfDayToISO(selectedTime),
|
||||
function: Status(
|
||||
code: code ?? 'switch_1',
|
||||
value: temp,
|
||||
),
|
||||
days: _convertSelectedDaysToStrings(selectedDays),
|
||||
scheduleId: schedule.scheduleId,
|
||||
);
|
||||
Navigator.pop(ctx, entry);
|
||||
},
|
||||
SizedBox(
|
||||
width: 100,
|
||||
child: OutlinedButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(ctx, null);
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 100,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
dynamic temp;
|
||||
if (deviceType == 'CUR_2') {
|
||||
temp = functionOn! ? 'open' : 'close';
|
||||
} else {
|
||||
temp = functionOn;
|
||||
}
|
||||
final entry = ScheduleEntry(
|
||||
category: schedule?.category ?? 'switch_1',
|
||||
time: _formatTimeOfDayToISO(selectedTime),
|
||||
function: Status(
|
||||
code: code ?? 'switch_1',
|
||||
value: temp,
|
||||
),
|
||||
days: _convertSelectedDaysToStrings(selectedDays),
|
||||
scheduleId: schedule.scheduleId,
|
||||
);
|
||||
Navigator.pop(ctx, entry);
|
||||
},
|
||||
child: const Text('Save'),
|
||||
)),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
@ -153,7 +153,6 @@ class EditUserModel {
|
||||
final String? jobTitle; // can be empty
|
||||
final String roleType; // e.g. "ADMIN"
|
||||
final List<UserSpaceModel> spaces;
|
||||
final String? companyName;
|
||||
|
||||
EditUserModel({
|
||||
required this.uuid,
|
||||
@ -168,7 +167,6 @@ class EditUserModel {
|
||||
required this.jobTitle,
|
||||
required this.roleType,
|
||||
required this.spaces,
|
||||
this.companyName,
|
||||
});
|
||||
|
||||
/// Create a [UserData] from JSON data
|
||||
@ -184,7 +182,6 @@ class EditUserModel {
|
||||
invitedBy: json['invitedBy'] as String,
|
||||
phoneNumber: json['phoneNumber'] ?? '',
|
||||
jobTitle: json['jobTitle'] ?? '',
|
||||
companyName: json['companyName'] as String?,
|
||||
roleType: json['roleType'] as String,
|
||||
spaces: (json['spaces'] as List<dynamic>)
|
||||
.map((e) => UserSpaceModel.fromJson(e as Map<String, dynamic>))
|
||||
|
@ -12,7 +12,7 @@ class RolesUserModel {
|
||||
final dynamic jobTitle;
|
||||
final dynamic createdDate;
|
||||
final dynamic createdTime;
|
||||
final String? companyName;
|
||||
|
||||
RolesUserModel({
|
||||
required this.uuid,
|
||||
required this.createdAt,
|
||||
@ -27,7 +27,6 @@ class RolesUserModel {
|
||||
this.jobTitle,
|
||||
required this.createdDate,
|
||||
required this.createdTime,
|
||||
this.companyName,
|
||||
});
|
||||
|
||||
factory RolesUserModel.fromJson(Map<String, dynamic> json) {
|
||||
@ -48,7 +47,6 @@ class RolesUserModel {
|
||||
: json['jobTitle'],
|
||||
createdDate: json['createdDate'],
|
||||
createdTime: json['createdTime'],
|
||||
companyName: json['companyName'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
||||
final TextEditingController lastNameController = TextEditingController();
|
||||
final TextEditingController emailController = TextEditingController();
|
||||
final TextEditingController phoneController = TextEditingController();
|
||||
final TextEditingController companyNameController = TextEditingController();
|
||||
final TextEditingController jobTitleController = TextEditingController();
|
||||
final TextEditingController roleSearchController = TextEditingController();
|
||||
|
||||
bool? isCompleteBasics;
|
||||
@ -352,7 +352,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
||||
bool res = await UserPermissionApi().sendInviteUser(
|
||||
email: emailController.text,
|
||||
firstName: firstNameController.text,
|
||||
companyName: companyNameController.text,
|
||||
jobTitle: jobTitleController.text,
|
||||
lastName: lastNameController.text,
|
||||
phoneNumber: phoneController.text,
|
||||
roleUuid: roleSelected,
|
||||
@ -405,7 +405,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
||||
bool res = await UserPermissionApi().editInviteUser(
|
||||
userId: event.userId,
|
||||
firstName: firstNameController.text,
|
||||
companyName: companyNameController.text,
|
||||
jobTitle: jobTitleController.text,
|
||||
lastName: lastNameController.text,
|
||||
phoneNumber: phoneController.text,
|
||||
roleUuid: roleSelected,
|
||||
@ -455,7 +455,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
||||
Future<void> checkEmail(
|
||||
CheckEmailEvent event, Emitter<UsersState> emit) async {
|
||||
emit(UsersLoadingState());
|
||||
String? res = await UserPermissionApi().checkEmail(
|
||||
String? res = await UserPermissionApi().checkEmail(
|
||||
emailController.text,
|
||||
);
|
||||
checkEmailValid = res!;
|
||||
@ -529,7 +529,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
||||
lastNameController.text = res.lastName;
|
||||
emailController.text = res.email;
|
||||
phoneController.text = res.phoneNumber ?? '';
|
||||
companyNameController.text = res.companyName ?? '';
|
||||
jobTitleController.text = res.jobTitle ?? '';
|
||||
res.roleType;
|
||||
res.spaces.map((space) {
|
||||
selectedIds.add(space.uuid);
|
||||
@ -645,7 +645,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
||||
lastNameController.dispose();
|
||||
emailController.dispose();
|
||||
phoneController.dispose();
|
||||
companyNameController.dispose();
|
||||
jobTitleController.dispose();
|
||||
roleSearchController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
@ -317,7 +317,7 @@ class BasicsView extends StatelessWidget {
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'Company Name',
|
||||
'Job Title',
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 13,
|
||||
@ -328,11 +328,11 @@ class BasicsView extends StatelessWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextFormField(
|
||||
controller: _blocRole.companyNameController,
|
||||
controller: _blocRole.jobTitleController,
|
||||
style:
|
||||
const TextStyle(color: ColorsManager.blackColor),
|
||||
decoration: inputTextFormDeco(
|
||||
hintText: 'Company Name (Optional)')
|
||||
hintText: "Job Title (Optional)")
|
||||
.copyWith(
|
||||
hintStyle: context.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
|
@ -411,7 +411,7 @@ class UsersPage extends StatelessWidget {
|
||||
titles: const [
|
||||
"Full Name",
|
||||
"Email Address",
|
||||
"Company Name",
|
||||
"Job Title",
|
||||
"Role",
|
||||
"Creation Date",
|
||||
"Creation Time",
|
||||
@ -424,7 +424,7 @@ class UsersPage extends StatelessWidget {
|
||||
return [
|
||||
Text('${user.firstName} ${user.lastName}'),
|
||||
Text(user.email),
|
||||
Center(child: Text(user.companyName ?? '-')),
|
||||
Text(user.jobTitle),
|
||||
Text(user.roleType ?? ''),
|
||||
Text(user.createdDate ?? ''),
|
||||
Text(user.createdTime ?? ''),
|
||||
|
@ -170,45 +170,45 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onLoadScenes(
|
||||
LoadScenes event, Emitter<RoutineState> emit) async {
|
||||
emit(state.copyWith(isLoading: true, errorMessage: null));
|
||||
List<ScenesModel> scenes = [];
|
||||
try {
|
||||
BuildContext context = NavigationService.navigatorKey.currentContext!;
|
||||
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
if (createRoutineBloc.selectedSpaceId == '' &&
|
||||
createRoutineBloc.selectedCommunityId == '') {
|
||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||
for (var communityId in spaceBloc.state.selectedCommunities) {
|
||||
List<String> spacesList =
|
||||
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||
for (var spaceId in spacesList) {
|
||||
scenes.addAll(
|
||||
await SceneApi.getScenes(spaceId, communityId, projectUuid));
|
||||
}
|
||||
Future<void> _onLoadScenes(
|
||||
LoadScenes event, Emitter<RoutineState> emit) async {
|
||||
emit(state.copyWith(isLoading: true, errorMessage: null));
|
||||
List<ScenesModel> scenes = [];
|
||||
try {
|
||||
BuildContext context = NavigationService.navigatorKey.currentContext!;
|
||||
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
if (createRoutineBloc.selectedSpaceId == '' &&
|
||||
createRoutineBloc.selectedCommunityId == '') {
|
||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||
for (var communityId in spaceBloc.state.selectedCommunities) {
|
||||
List<String> spacesList =
|
||||
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||
for (var spaceId in spacesList) {
|
||||
scenes.addAll(
|
||||
await SceneApi.getScenes(spaceId, communityId, projectUuid));
|
||||
}
|
||||
} else {
|
||||
scenes.addAll(await SceneApi.getScenes(
|
||||
createRoutineBloc.selectedSpaceId,
|
||||
createRoutineBloc.selectedCommunityId,
|
||||
projectUuid));
|
||||
}
|
||||
|
||||
emit(state.copyWith(
|
||||
scenes: scenes,
|
||||
isLoading: false,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
isLoading: false,
|
||||
loadScenesErrorMessage: 'Failed to load scenes',
|
||||
errorMessage: '',
|
||||
loadAutomationErrorMessage: '',
|
||||
scenes: scenes));
|
||||
} else {
|
||||
scenes.addAll(await SceneApi.getScenes(
|
||||
createRoutineBloc.selectedSpaceId,
|
||||
createRoutineBloc.selectedCommunityId,
|
||||
projectUuid));
|
||||
}
|
||||
|
||||
emit(state.copyWith(
|
||||
scenes: scenes,
|
||||
isLoading: false,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
isLoading: false,
|
||||
loadScenesErrorMessage: 'Failed to load scenes',
|
||||
errorMessage: '',
|
||||
loadAutomationErrorMessage: '',
|
||||
scenes: scenes));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onLoadAutomation(
|
||||
LoadAutomation event, Emitter<RoutineState> emit) async {
|
||||
@ -936,15 +936,16 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||
for (var communityId in spaceBloc.state.selectedCommunities) {
|
||||
List<String> spacesList =
|
||||
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||
|
||||
devices.addAll(await DevicesManagementApi()
|
||||
.fetchDevices(projectUuid, spacesId: spacesList));
|
||||
for (var spaceId in spacesList) {
|
||||
devices.addAll(await DevicesManagementApi()
|
||||
.fetchDevices(communityId, spaceId, projectUuid));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
devices.addAll(await DevicesManagementApi().fetchDevices(
|
||||
projectUuid,
|
||||
spacesId: [createRoutineBloc.selectedSpaceId],
|
||||
));
|
||||
createRoutineBloc.selectedCommunityId,
|
||||
createRoutineBloc.selectedSpaceId,
|
||||
projectUuid));
|
||||
}
|
||||
|
||||
emit(state.copyWith(isLoading: false, devices: devices));
|
||||
|
@ -96,7 +96,9 @@ class _WallPresenceSensorState extends State<FlushPresenceSensor> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DialogHeader('Presence Sensor'),
|
||||
DialogHeader(widget.dialogType == 'THEN'
|
||||
? 'Presence Sensor Functions'
|
||||
: 'Presence Sensor Condition'),
|
||||
Expanded(child: _buildMainContent(context, state)),
|
||||
_buildDialogFooter(context, state),
|
||||
],
|
||||
|
@ -0,0 +1,43 @@
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,22 +40,4 @@ class PaginatedDataModel<T> extends Equatable {
|
||||
totalItems,
|
||||
totalPages,
|
||||
];
|
||||
|
||||
PaginatedDataModel<T> copyWith({
|
||||
List<T>? data,
|
||||
int? page,
|
||||
int? size,
|
||||
bool? hasNext,
|
||||
int? totalItems,
|
||||
int? totalPages,
|
||||
}) {
|
||||
return PaginatedDataModel<T>(
|
||||
data: data ?? this.data,
|
||||
page: page ?? this.page,
|
||||
size: size ?? this.size,
|
||||
hasNext: hasNext ?? this.hasNext,
|
||||
totalItems: totalItems ?? this.totalItems,
|
||||
totalPages: totalPages ?? this.totalPages,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/presen
|
||||
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/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';
|
||||
@ -49,7 +49,7 @@ class _SpaceManagementPageState extends State<SpaceManagementPage> {
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => SpaceDetailsBloc(
|
||||
UniqueSubspacesDecorator(
|
||||
UniqueSpaceDetailsSpacesDecoratorService(
|
||||
RemoteSpaceDetailsService(httpService: HTTPService()),
|
||||
),
|
||||
),
|
||||
|
@ -30,10 +30,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 +53,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 +70,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 +132,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);
|
||||
|
||||
@ -155,13 +186,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 +204,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 +221,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,8 +236,11 @@ 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);
|
||||
|
||||
@ -240,6 +277,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
painter: SpacesConnectionsArrowPainter(
|
||||
connections: connections,
|
||||
positions: _positions,
|
||||
cardWidths: _cardWidths,
|
||||
highlightedUuids: highlightedUuids,
|
||||
),
|
||||
child: Stack(alignment: AlignmentDirectional.center, children: widgets),
|
||||
@ -271,6 +309,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,14 +317,10 @@ 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,
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -305,7 +340,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 +349,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
child: Opacity(
|
||||
opacity: 0.2,
|
||||
child: SizedBox(
|
||||
width: _cardWidth,
|
||||
width: cardWidth,
|
||||
height: _cardHeight,
|
||||
child: spaceCard,
|
||||
),
|
||||
@ -330,7 +365,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
|
||||
);
|
||||
|
||||
final targetPos = Offset(
|
||||
position.dx + _cardWidth + (_horizontalSpacing / 4) - 20,
|
||||
position.dx + cardWidth + (_horizontalSpacing / 4) - 20,
|
||||
position.dy,
|
||||
);
|
||||
widgets.add(_buildDropTarget(parent, community, i + 1, targetPos));
|
||||
@ -418,17 +453,17 @@ 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,
|
||||
|
@ -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),
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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,8 @@ 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/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||
|
||||
class SpaceManagementCommunityStructure extends StatelessWidget {
|
||||
@ -13,28 +15,44 @@ class SpaceManagementCommunityStructure extends StatelessWidget {
|
||||
final selectionBloc = context.watch<CommunitiesTreeSelectionBloc>().state;
|
||||
final selectedCommunity = selectionBloc.selectedCommunity;
|
||||
final selectedSpace = selectionBloc.selectedSpace;
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const CommunityStructureHeader(),
|
||||
Visibility(
|
||||
visible: selectedCommunity!.spaces.isNotEmpty,
|
||||
replacement: _buildEmptyWidget(selectedCommunity),
|
||||
child: _buildCanvas(selectedCommunity, 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,
|
||||
child: CreateSpaceButton(
|
||||
communityUuid: selectedCommunity.uuid,
|
||||
),
|
||||
),
|
||||
spacer,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -0,0 +1,64 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/services/delete_space_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';
|
||||
|
||||
final class RemoteDeleteSpaceService implements DeleteSpaceService {
|
||||
const RemoteDeleteSpaceService(this._httpService);
|
||||
|
||||
final HTTPService _httpService;
|
||||
|
||||
@override
|
||||
Future<void> delete(DeleteSpaceParam param) async {
|
||||
try {
|
||||
await _httpService.delete(
|
||||
path: await _makeUrl(param),
|
||||
expectedResponseModel: (json) {
|
||||
final response = json as Map<String, dynamic>;
|
||||
final hasSuccessfullyDeletedSpace = response['success'] as bool? ?? false;
|
||||
|
||||
if (!hasSuccessfullyDeletedSpace) {
|
||||
throw APIException('Failed to delete space');
|
||||
}
|
||||
|
||||
return hasSuccessfullyDeletedSpace;
|
||||
},
|
||||
);
|
||||
} on DioException catch (e) {
|
||||
final message = e.response?.data as Map<String, dynamic>?;
|
||||
throw APIException(_getErrorMessageFromBody(message));
|
||||
} catch (e) {
|
||||
throw APIException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
String _getErrorMessageFromBody(Map<String, dynamic>? body) {
|
||||
if (body == null) return 'Failed to delete space';
|
||||
final error = body['error'] as Map<String, dynamic>?;
|
||||
final errorMessage = error?['message'] as String? ?? '';
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
Future<String> _makeUrl(DeleteSpaceParam param) async {
|
||||
final projectUuid = await ProjectManager.getProjectUUID();
|
||||
if (projectUuid == null) {
|
||||
throw APIException('Project UUID is not set');
|
||||
}
|
||||
|
||||
if (param.communityUuid.isEmpty) {
|
||||
throw APIException('Community UUID is not set');
|
||||
}
|
||||
|
||||
if (param.spaceUuid.isEmpty) {
|
||||
throw APIException('Space UUID is not set');
|
||||
}
|
||||
|
||||
return ApiEndpoints.deleteSpace
|
||||
.replaceAll('{projectId}', projectUuid)
|
||||
.replaceAll('{communityId}', param.communityUuid)
|
||||
.replaceAll('{spaceId}', param.spaceUuid);
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
class DeleteSpaceParam {
|
||||
const DeleteSpaceParam({
|
||||
required this.spaceUuid,
|
||||
required this.communityUuid,
|
||||
});
|
||||
|
||||
final String spaceUuid;
|
||||
final String communityUuid;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart';
|
||||
|
||||
abstract interface class DeleteSpaceService {
|
||||
Future<void> delete(DeleteSpaceParam param);
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/services/delete_space_service.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
|
||||
part 'delete_space_event.dart';
|
||||
part 'delete_space_state.dart';
|
||||
|
||||
class DeleteSpaceBloc extends Bloc<DeleteSpaceEvent, DeleteSpaceState> {
|
||||
DeleteSpaceBloc(this._deleteSpaceService) : super(DeleteSpaceInitial()) {
|
||||
on<DeleteSpace>(_onDeleteSpace);
|
||||
}
|
||||
|
||||
final DeleteSpaceService _deleteSpaceService;
|
||||
|
||||
Future<void> _onDeleteSpace(
|
||||
DeleteSpace event,
|
||||
Emitter<DeleteSpaceState> emit,
|
||||
) async {
|
||||
emit(DeleteSpaceLoading());
|
||||
try {
|
||||
await _deleteSpaceService.delete(event.param);
|
||||
emit(const DeleteSpaceSuccess('Space deleted successfully'));
|
||||
} on APIException catch (e) {
|
||||
emit(DeleteSpaceFailure(e.message));
|
||||
} catch (e) {
|
||||
emit(DeleteSpaceFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
part of 'delete_space_bloc.dart';
|
||||
|
||||
sealed class DeleteSpaceEvent extends Equatable {
|
||||
const DeleteSpaceEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class DeleteSpace extends DeleteSpaceEvent {
|
||||
const DeleteSpace(this.param);
|
||||
|
||||
final DeleteSpaceParam param;
|
||||
|
||||
@override
|
||||
List<Object> get props => [param];
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
part of 'delete_space_bloc.dart';
|
||||
|
||||
sealed class DeleteSpaceState extends Equatable {
|
||||
const DeleteSpaceState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class DeleteSpaceInitial extends DeleteSpaceState {}
|
||||
|
||||
final class DeleteSpaceLoading extends DeleteSpaceState {}
|
||||
|
||||
final class DeleteSpaceSuccess extends DeleteSpaceState {
|
||||
const DeleteSpaceSuccess(this.successMessage);
|
||||
|
||||
final String successMessage;
|
||||
|
||||
@override
|
||||
List<Object> get props => [successMessage];
|
||||
}
|
||||
|
||||
final class DeleteSpaceFailure extends DeleteSpaceState {
|
||||
const DeleteSpaceFailure(this.errorMessage);
|
||||
|
||||
final String errorMessage;
|
||||
|
||||
@override
|
||||
List<Object> get props => [errorMessage];
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
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/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog_form.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_loading_widget.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_status_widget.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class DeleteSpaceDialog extends StatelessWidget {
|
||||
const DeleteSpaceDialog({
|
||||
required this.space,
|
||||
required this.community,
|
||||
required this.onSuccess,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final SpaceModel space;
|
||||
final CommunityModel community;
|
||||
final void Function() onSuccess;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => DeleteSpaceBloc(
|
||||
RemoteDeleteSpaceService(HTTPService()),
|
||||
),
|
||||
child: Builder(
|
||||
builder: (context) => Dialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Container(
|
||||
padding: const EdgeInsetsDirectional.all(32),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: context.screenWidth * 0.2,
|
||||
),
|
||||
child: BlocConsumer<DeleteSpaceBloc, DeleteSpaceState>(
|
||||
listener: (context, state) {
|
||||
if (state case DeleteSpaceSuccess()) onSuccess();
|
||||
},
|
||||
builder: (context, state) => switch (state) {
|
||||
DeleteSpaceInitial() => DeleteSpaceDialogForm(
|
||||
space: space,
|
||||
communityUuid: community.uuid,
|
||||
),
|
||||
DeleteSpaceLoading() => const DeleteSpaceLoadingWidget(),
|
||||
DeleteSpaceSuccess() => DeleteSpaceStatusWidget(
|
||||
message: state.successMessage,
|
||||
icon: const Icon(
|
||||
Icons.check_circle,
|
||||
size: 92,
|
||||
color: ColorsManager.goodGreen,
|
||||
),
|
||||
),
|
||||
DeleteSpaceFailure() => DeleteSpaceStatusWidget(
|
||||
message: state.errorMessage,
|
||||
icon: const Icon(
|
||||
Icons.error,
|
||||
size: 92,
|
||||
color: ColorsManager.red,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
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/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/domain/params/delete_space_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.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 DeleteSpaceDialogForm extends StatelessWidget {
|
||||
const DeleteSpaceDialogForm({
|
||||
required this.space,
|
||||
required this.communityUuid,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final SpaceModel space;
|
||||
final String communityUuid;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SvgPicture.asset(Assets.xDelete, width: 36, height: 36),
|
||||
const SizedBox(height: 16),
|
||||
SelectableText(
|
||||
'Delete Space',
|
||||
textAlign: TextAlign.center,
|
||||
style: context.textTheme.titleLarge?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SelectableText(
|
||||
'Are you sure you want to delete this space? This action is irreversible',
|
||||
textAlign: TextAlign.center,
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
color: ColorsManager.lightGreyColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FilledButton(
|
||||
style: _buildButtonStyle(
|
||||
context,
|
||||
color: ColorsManager.grey25,
|
||||
textColor: ColorsManager.blackColor,
|
||||
),
|
||||
onPressed: Navigator.of(context).pop,
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: FilledButton(
|
||||
style: _buildButtonStyle(
|
||||
context,
|
||||
color: ColorsManager.semiTransparentRed,
|
||||
textColor: ColorsManager.whiteColors,
|
||||
),
|
||||
onPressed: () {
|
||||
context.read<DeleteSpaceBloc>().add(
|
||||
DeleteSpace(
|
||||
DeleteSpaceParam(
|
||||
spaceUuid: space.uuid,
|
||||
communityUuid: communityUuid,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Delete'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
ButtonStyle _buildButtonStyle(
|
||||
BuildContext context, {
|
||||
required Color color,
|
||||
required Color textColor,
|
||||
}) {
|
||||
return FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
backgroundColor: color,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
foregroundColor: textColor,
|
||||
textStyle: context.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 14,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DeleteSpaceLoadingWidget extends StatelessWidget {
|
||||
const DeleteSpaceLoadingWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SizedBox.square(
|
||||
dimension: 32,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class DeleteSpaceStatusWidget extends StatelessWidget {
|
||||
const DeleteSpaceStatusWidget({
|
||||
required this.message,
|
||||
required this.icon,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String message;
|
||||
final Widget icon;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 16,
|
||||
children: [
|
||||
icon,
|
||||
SelectableText(
|
||||
message,
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize: 22,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
child: const Text('Close'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -2,23 +2,27 @@ import 'package:syncrow_web/pages/space_management_v2/modules/space_details/doma
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/services/space_details_service.dart';
|
||||
|
||||
class UniqueSubspacesDecorator implements SpaceDetailsService {
|
||||
class UniqueSpaceDetailsSpacesDecoratorService implements SpaceDetailsService {
|
||||
final SpaceDetailsService _decoratee;
|
||||
|
||||
const UniqueSubspacesDecorator(this._decoratee);
|
||||
const UniqueSpaceDetailsSpacesDecoratorService(this._decoratee);
|
||||
|
||||
@override
|
||||
Future<SpaceDetailsModel> getSpaceDetails(LoadSpaceDetailsParam param) async {
|
||||
final response = await _decoratee.getSpaceDetails(param);
|
||||
|
||||
final uniqueSubspaces = <String, Subspace>{};
|
||||
final duplicateNames = <String>{};
|
||||
|
||||
for (final subspace in response.subspaces) {
|
||||
final normalizedName = subspace.name.trim().toLowerCase();
|
||||
if (!uniqueSubspaces.containsKey(normalizedName)) {
|
||||
if (uniqueSubspaces.containsKey(normalizedName)) {
|
||||
duplicateNames.add(normalizedName);
|
||||
} else {
|
||||
uniqueSubspaces[normalizedName] = subspace;
|
||||
}
|
||||
}
|
||||
duplicateNames.forEach(uniqueSubspaces.remove);
|
||||
|
||||
return response.copyWith(
|
||||
subspaces: uniqueSubspaces.values.toList(),
|
@ -10,7 +10,12 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class AddDeviceTypeWidget extends StatefulWidget {
|
||||
const AddDeviceTypeWidget({super.key});
|
||||
const AddDeviceTypeWidget({
|
||||
super.key,
|
||||
this.initialProducts = const [],
|
||||
});
|
||||
|
||||
final List<Product> initialProducts;
|
||||
|
||||
@override
|
||||
State<AddDeviceTypeWidget> createState() => _AddDeviceTypeWidgetState();
|
||||
@ -18,6 +23,16 @@ class AddDeviceTypeWidget extends StatefulWidget {
|
||||
|
||||
class _AddDeviceTypeWidgetState extends State<AddDeviceTypeWidget> {
|
||||
final Map<Product, int> _selectedProducts = {};
|
||||
final Map<Product, int> _initialProductCounts = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
for (final product in widget.initialProducts) {
|
||||
_initialProductCounts[product] = (_initialProductCounts[product] ?? 0) + 1;
|
||||
}
|
||||
_selectedProducts.addAll(_initialProductCounts);
|
||||
}
|
||||
|
||||
void _onIncrement(Product product) {
|
||||
setState(() {
|
||||
@ -27,8 +42,12 @@ class _AddDeviceTypeWidgetState extends State<AddDeviceTypeWidget> {
|
||||
|
||||
void _onDecrement(Product product) {
|
||||
setState(() {
|
||||
if ((_selectedProducts[product] ?? 0) > 0) {
|
||||
_selectedProducts[product] = _selectedProducts[product]! - 1;
|
||||
final initialCount = _initialProductCounts[product] ?? 0;
|
||||
final currentCount = _selectedProducts[product] ?? 0;
|
||||
if (currentCount > initialCount) {
|
||||
_selectedProducts[product] = currentCount - 1;
|
||||
} else if (currentCount > 0 && initialCount == 0) {
|
||||
_selectedProducts[product] = currentCount - 1;
|
||||
if (_selectedProducts[product] == 0) {
|
||||
_selectedProducts.remove(product);
|
||||
}
|
||||
@ -63,7 +82,22 @@ class _AddDeviceTypeWidgetState extends State<AddDeviceTypeWidget> {
|
||||
actions: [
|
||||
SpaceDetailsActionButtons(
|
||||
onSave: () {
|
||||
final result = _selectedProducts.entries
|
||||
final resultMap = <Product, int>{};
|
||||
resultMap.addAll(_selectedProducts);
|
||||
|
||||
for (final entry in _initialProductCounts.entries) {
|
||||
final product = entry.key;
|
||||
final initialCount = entry.value;
|
||||
final currentCount = resultMap[product] ?? 0;
|
||||
|
||||
if (currentCount > initialCount) {
|
||||
resultMap[product] = currentCount - initialCount;
|
||||
} else {
|
||||
resultMap.remove(product);
|
||||
}
|
||||
}
|
||||
|
||||
final result = resultMap.entries
|
||||
.expand((entry) => List.generate(entry.value, (_) => entry.key))
|
||||
.toList();
|
||||
Navigator.of(context).pop(result);
|
||||
|
@ -205,7 +205,14 @@ class _AssignTagsDialogState extends State<AssignTagsDialog> {
|
||||
onCancel: () async {
|
||||
final newProducts = await showDialog<List<Product>>(
|
||||
context: context,
|
||||
builder: (context) => const AddDeviceTypeWidget(),
|
||||
builder: (context) => AddDeviceTypeWidget(
|
||||
initialProducts: [
|
||||
..._space.productAllocations.map((e) => e.product),
|
||||
..._space.subspaces
|
||||
.expand((s) => s.productAllocations)
|
||||
.map((e) => e.product),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (newProducts == null || newProducts.isEmpty) return;
|
||||
|
@ -181,7 +181,7 @@ class VisitorPasswordBloc
|
||||
effectiveTimeTimeStamp = selectedTimestamp;
|
||||
startTimeAccess = selectedDateTime.toString().split('.').first;
|
||||
} else {
|
||||
|
||||
// END TIME VALIDATION
|
||||
if (effectiveTimeTimeStamp != null &&
|
||||
selectedTimestamp < effectiveTimeTimeStamp!) {
|
||||
await showDialog<void>(
|
||||
|
@ -12,16 +12,20 @@ import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
class DevicesManagementApi {
|
||||
Future<List<AllDevicesModel>> fetchDevices(String projectId,
|
||||
{List<String>? spacesId}) async {
|
||||
Future<List<AllDevicesModel>> fetchDevices(
|
||||
String communityId, String spaceId, String projectId) async {
|
||||
try {
|
||||
final response = await HTTPService().get(
|
||||
path: ApiEndpoints.getSpaceDevices.replaceAll('{projectId}', projectId),
|
||||
queryParameters: {if (spacesId != null) 'spaces': spacesId},
|
||||
path: communityId.isNotEmpty && spaceId.isNotEmpty
|
||||
? ApiEndpoints.getSpaceDevices
|
||||
.replaceAll('{spaceUuid}', spaceId)
|
||||
.replaceAll('{communityUuid}', communityId)
|
||||
.replaceAll('{projectId}', projectId)
|
||||
: ApiEndpoints.getAllDevices.replaceAll('{projectId}', projectId),
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
final List<dynamic> jsonData = json['data'] as List<dynamic>;
|
||||
final List<AllDevicesModel> devicesList = jsonData.map((jsonItem) {
|
||||
List<dynamic> jsonData = json['data'];
|
||||
List<AllDevicesModel> devicesList = jsonData.map((jsonItem) {
|
||||
return AllDevicesModel.fromJson(jsonItem);
|
||||
}).toList();
|
||||
return devicesList;
|
||||
@ -412,4 +416,5 @@ class DevicesManagementApi {
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user