Compare commits

..
Author SHA1 Message Date
Faris Armoush 9d27ed2dc5 SP-1506 rework, coloring and padding. 2025-05-25 11:13:24 +03:00
Faris Armoush a878b9328a SP-1493 rework, can select a subspace in sidebar even when the space has no child-spaces. 2025-05-25 11:06:36 +03:00
Faris Armoush 0f9227a6f5 Merge pull request #200 from SyncrowIOT/SP-1591-FE-Implement-Space-Level-Structure-Selection-and-Air-Quality-Device-Dropdown
Sp 1591 fe implement space level structure selection and air quality device dropdown
2025-05-22 15:59:19 +03:00
Faris Armoush 5b13962d41 removed unnecessary * 1 calculation of height. 2025-05-22 15:57:03 +03:00
Faris Armoush 8c53d5322a SP-1591 2025-05-22 15:53:18 +03:00
Faris Armoush af4d37939b Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1591-FE-Implement-Space-Level-Structure-Selection-and-Air-Quality-Device-Dropdown 2025-05-22 15:48:47 +03:00
Faris Armoush d43c1847ff SP-1591 2025-05-22 15:44:19 +03:00
Faris Armoush 4c5b390887 Fixed typos. 2025-05-22 15:42:49 +03:00
Faris Armoush 5eeac01666 cannot select a community in AirQualityDataLoadingStrategy. 2025-05-22 15:35:04 +03:00
Faris Armoush 717d698378 can select child spaces with children in AirQualityDataLoadingStrategy. 2025-05-22 15:23:42 +03:00
Faris Armoush 9adbbb9a2d Integrated and implemented devices dropdown into the newly created widget AirQualityEndSideWidget. 2025-05-22 15:19:50 +03:00
Faris Armoush e792dbd72f SP-1591/ Implement business logic in AirQualityDataLoadingStrategy for community structure loading strategy. 2025-05-22 14:58:42 +03:00
Faris Armoush d2eea33714 Prepared AirQualityView layout and structure with PlaceHolder widgets. 2025-05-22 12:24:13 +03:00
Faris Armoush 5a61647fe4 Prepared and created the necessary component for the air quality loading strategy for the side bar selection, and for loading data in different parts of the UI. 2025-05-21 16:49:30 +03:00
Faris Armoush 568b6be354 Created AirQualityView widget for the new Air Quality analytics module. 2025-05-21 16:46:38 +03:00
53 changed files with 1548 additions and 2000 deletions
+5 -18
View File
@@ -17,8 +17,7 @@ class TagDialogTextfieldDropdown extends StatefulWidget {
}) : super(key: key); }) : super(key: key);
@override @override
_DialogTextfieldDropdownState createState() => _DialogTextfieldDropdownState createState() => _DialogTextfieldDropdownState();
_DialogTextfieldDropdownState();
} }
class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> { class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
@@ -37,12 +36,6 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
_focusNode.addListener(() { _focusNode.addListener(() {
if (!_focusNode.hasFocus) { if (!_focusNode.hasFocus) {
// Call onSelected when focus is lost
final selectedTag = _filteredItems.firstWhere(
(tag) => tag.tag == _controller.text,
orElse: () => Tag(tag: _controller.text),
);
widget.onSelected(selectedTag);
_closeDropdown(); _closeDropdown();
} }
}); });
@@ -50,9 +43,7 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
void _filterItems() { void _filterItems() {
setState(() { setState(() {
_filteredItems = widget.items; _filteredItems = widget.items.where((tag) => tag.product?.uuid == widget.product).toList();
// .where((tag) => tag.product?.uuid == widget.product)
// .toList();
}); });
} }
@@ -121,9 +112,7 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyMedium .bodyMedium
?.copyWith( ?.copyWith(color: ColorsManager.textPrimaryColor)),
color: ColorsManager
.textPrimaryColor)),
onTap: () { onTap: () {
_controller.text = tag.tag ?? ''; _controller.text = tag.tag ?? '';
widget.onSelected(tag); widget.onSelected(tag);
@@ -167,15 +156,13 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
controller: _controller, controller: _controller,
focusNode: _focusNode, focusNode: _focusNode,
onFieldSubmitted: (value) { onFieldSubmitted: (value) {
final selectedTag = _filteredItems.firstWhere( final selectedTag = _filteredItems.firstWhere((tag) => tag.tag == value,
(tag) => tag.tag == value,
orElse: () => Tag(tag: value)); orElse: () => Tag(tag: value));
widget.onSelected(selectedTag); widget.onSelected(selectedTag);
_closeDropdown(); _closeDropdown();
}, },
onTapOutside: (event) { onTapOutside: (event) {
widget.onSelected(_filteredItems.firstWhere( widget.onSelected(_filteredItems.firstWhere((tag) => tag.tag == _controller.text,
(tag) => tag.tag == _controller.text,
orElse: () => Tag(tag: _controller.text))); orElse: () => Tag(tag: _controller.text)));
_closeDropdown(); _closeDropdown();
}, },
-16
View File
@@ -1,16 +0,0 @@
import 'enums.dart';
class GenericException implements Exception {
final ExceptionType type;
final String errorMessage;
const GenericException(
{required this.type, this.errorMessage = "Unknown Error"});
@override
String toString() {
return errorMessage;
}
List<Object?> get props => [type, errorMessage];
}
-36
View File
@@ -1,36 +0,0 @@
import 'package:dio/dio.dart';
import '../../utils/constants/api_const.dart';
class DioInstance {
Dio? _dio;
Dio get dio => _dio ?? _instantiate();
String? baseUrl;
DioInstance({this.baseUrl});
Dio _instantiate() {
Dio dio = Dio(
BaseOptions(
baseUrl: baseUrl ?? '${ApiEndpoints.baseUrl}/',
receiveDataWhenStatusError: true,
headers: {
'content_Type': 'application/json',
},
),
);
dio.interceptors.add(
LogInterceptor(
responseHeader: false,
requestHeader: false,
requestBody: true,
responseBody: true,
),
);
return dio;
}
}
-3
View File
@@ -1,3 +0,0 @@
class EndPoints {
static const String fetchCommunities = 'projects/{projectUuid}/communities';
}
-50
View File
@@ -1,50 +0,0 @@
enum RequestType {
get,
post,
delete,
put,
}
enum ExceptionType {
notAuthenticated,
connectionError,
// related to http status code exceptions
notAuthorized,
notFound,
internalServerException,
serviceUnavailableException,
pageGone,
// related to bad request status code
// related to auth requests
invalidCredentials,
solutionAlreadySunmitted,
invalidValidation,
// other
other,
}
enum ExceptionMessage {
NOT_AUTHENTICATED,
INVALID_CREDENTIALS,
The_password_field_must_be_at_least_8_characters,
SOLUTION_ALREADY_SUBMITTED,
you_are_not_authorized,
page_not_found,
page_gone,
INTERNAL_SERVER_ERROR,
service_unavailable,
}
enum NotificationsType {
payment,
transporation,
product,
zero,
}
enum ImagesType {
assets,
svg,
network,
}
-121
View File
@@ -1,121 +0,0 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import '../../pages/auth/model/token.dart';
import 'custom_exceptions.dart';
import 'dio.dart';
import 'enums.dart';
class Request {
String endPoint;
bool? autherized;
bool? isFormData;
RequestType? method;
Map<String, dynamic>? headers;
final Map<String, dynamic>? queryParams;
Map<String, dynamic>? body;
Duration? receiveTimeout;
Request(
this.endPoint, {
this.autherized,
this.isFormData,
this.method,
this.headers,
this.queryParams,
this.body,
this.receiveTimeout,
}) {
headers = {
'content_Type': 'application/json',
'Accept': 'application/json',
};
}
Future<Map<String, dynamic>> sendRequest() async {
Response? response;
if (autherized != null && autherized!) {
final storage = const FlutterSecureStorage();
final token = await storage.read(key: Token.loginAccessTokenKey);
if (token != null) {
headers!["authorization"] = "Bearer $token";
}
}
try {
response = await DioInstance().dio.request(
endPoint,
queryParameters: queryParams,
data: isFormData != null && isFormData == true
? FormData.fromMap(body!)
: body,
options: Options(
method: method!.name.toUpperCase(),
headers: headers,
contentType: 'application/json',
receiveTimeout: receiveTimeout,
),
);
if (response.statusCode! >= 200 && response.statusCode! < 300) {
return response.data;
}
} on DioException catch (error) {
if (error.type == DioExceptionType.badResponse) {
throw badRequestException[error.response!.data["error"]] ??
const GenericException(
type: ExceptionType.other,
);
}
if (error.type == DioExceptionType.connectionError ||
error.type == DioExceptionType.connectionTimeout ||
error.type == DioExceptionType.receiveTimeout ||
error.type == DioExceptionType.sendTimeout ||
error.type == DioExceptionType.unknown) {
throw const GenericException(
type: ExceptionType.connectionError,
errorMessage: 'no_internet_connection',
);
}
}
return {};
}
Map<String, dynamic> toJson() => {
'endPoint': endPoint,
'method': method!.name.toUpperCase(),
'body': json.encode(body),
'headers': headers,
'queryParams': queryParams,
'autherized': autherized,
'isFormData': isFormData,
};
@override
String toString() {
return jsonEncode(toJson());
}
List<Object?> get props => [
endPoint,
autherized,
isFormData,
method,
headers,
queryParams,
body,
receiveTimeout,
];
}
Map<String, GenericException> badRequestException = {
ExceptionMessage.INVALID_CREDENTIALS.name: const GenericException(
type: ExceptionType.invalidCredentials,
errorMessage: "Invalid credentials ...",
),
"The password field must be at least 8 characters.": const GenericException(
type: ExceptionType.invalidValidation,
errorMessage: 'password must be 8 or more characters',
),
ExceptionMessage.NOT_AUTHENTICATED.name: const GenericException(
type: ExceptionType.notAuthenticated,
errorMessage: "not authenticated",
),
};
@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
abstract final class FetchAirQualityDataHelper {
const FetchAirQualityDataHelper._();
static void loadAirQualityData(
BuildContext context, {
required String communityUuid,
required String spaceUuid,
}) {
loadAnalyticsDevices(
context,
communityUuid: communityUuid,
spaceUuid: spaceUuid,
);
}
static void clearAllData(BuildContext context) {
context.read<AnalyticsDevicesBloc>().add(
const ClearAnalyticsDeviceEvent(),
);
context.read<RealtimeDeviceChangesBloc>().add(
const RealtimeDeviceChangesClosed(),
);
}
static void loadAnalyticsDevices(
BuildContext context, {
required String communityUuid,
required String spaceUuid,
}) {
context.read<AnalyticsDevicesBloc>().add(
LoadAnalyticsDevicesEvent(
param: GetAnalyticsDevicesParam(
communityUuid: communityUuid,
spaceUuid: spaceUuid,
deviceTypes: ['AQI'],
requestType: AnalyticsDeviceRequestType.energyManagement,
),
onSuccess: (device) {
context.read<RealtimeDeviceChangesBloc>()
..add(const RealtimeDeviceChangesClosed())
..add(RealtimeDeviceChangesStarted(device.uuid));
},
),
);
}
}
@@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart';
class AirQualityView extends StatelessWidget {
const AirQualityView({super.key});
static const _padding = EdgeInsetsDirectional.all(32);
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final isMediumOrLess = constraints.maxWidth <= 900;
final height = MediaQuery.sizeOf(context).height;
if (isMediumOrLess) {
return SingleChildScrollView(
padding: _padding,
child: Column(
spacing: 32,
children: [
SizedBox(
height: height * 1.2,
child: const AirQualityEndSideWidget(),
),
SizedBox(height: height * 0.5, child: const Placeholder()),
SizedBox(height: height * 0.5, child: const Placeholder()),
],
),
);
}
return SingleChildScrollView(
child: Container(
padding: _padding,
height: height,
child: const Column(
children: [
Expanded(
child: Row(
spacing: 32,
children: [
Expanded(
flex: 2,
child: Column(
spacing: 20,
children: [
Expanded(child: Placeholder()),
Expanded(child: Placeholder()),
],
),
),
Expanded(child: AirQualityEndSideWidget()),
],
),
),
],
),
),
);
},
);
}
}
@@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class AirQualityEndSideWidget extends StatelessWidget {
const AirQualityEndSideWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(
decoration: subSectionContainerDecoration.copyWith(
borderRadius: BorderRadius.circular(30),
),
padding: const EdgeInsetsDirectional.all(32),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(context),
Text(
'Device ID:',
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w400,
fontSize: 12,
),
),
const SizedBox(height: 6),
SelectableText(
context.watch<AnalyticsDevicesBloc>().state.selectedDevice?.uuid ??
'N/A',
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor,
fontWeight: FontWeight.w400,
fontSize: 12,
),
),
],
),
);
}
Widget _buildHeader(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
flex: 3,
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerStart,
child: SelectableText(
'AQI Sensor',
style: context.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w700,
color: ColorsManager.vividBlue.withValues(alpha: 0.6),
fontSize: 18,
),
),
),
),
const Spacer(),
Expanded(
flex: 2,
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerEnd,
child: AnalyticsDeviceDropdown(
onChanged: (value) {
context.read<AnalyticsDevicesBloc>().add(
SelectAnalyticsDeviceEvent(value),
);
FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges(
context,
deviceUuid: value.uuid,
);
},
),
),
),
],
);
}
}
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/views/air_quality_view.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/views/analytics_energy_management_view.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/views/analytics_energy_management_view.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/views/analytics_occupancy_view.dart';
@@ -10,6 +11,10 @@ enum AnalyticsPageTab {
occupancy( occupancy(
title: 'Occupancy', title: 'Occupancy',
child: AnalyticsOccupancyView(), child: AnalyticsOccupancyView(),
),
airQuality(
title: 'Air Quality',
child: AirQualityView(),
); );
const AnalyticsPageTab({ const AnalyticsPageTab({
@@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
@override
void onCommunitySelected(
BuildContext context,
CommunityModel community,
List<SpaceModel> spaces,
) {
// Do nothing
}
@override
void onSpaceSelected(
BuildContext context,
CommunityModel community,
SpaceModel space,
) {
final spaceTreeBloc = context.read<SpaceTreeBloc>();
final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
if (isSpaceSelected) {
clearData(context);
return;
}
spaceTreeBloc
..add(const SpaceTreeClearSelectionEvent())
..add(OnSpaceSelected(community, space.uuid ?? '', []));
FetchAirQualityDataHelper.loadAirQualityData(
context,
communityUuid: community.uuid,
spaceUuid: space.uuid ?? '',
);
}
@override
void onChildSpaceSelected(
BuildContext context,
CommunityModel community,
SpaceModel child,
) {
return onSpaceSelected(context, community, child);
}
@override
void clearData(BuildContext context) {
context.read<SpaceTreeBloc>().add(const SpaceTreeClearSelectionEvent());
FetchAirQualityDataHelper.clearAllData(context);
}
}
@@ -1,4 +1,5 @@
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/energy_management_data_loading_strategy.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/energy_management_data_loading_strategy.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart';
@@ -9,6 +10,7 @@ abstract final class AnalyticsDataLoadingStrategyFactory {
return switch (tab) { return switch (tab) {
AnalyticsPageTab.energyManagement => EnergyManagementDataLoadingStrategy(), AnalyticsPageTab.energyManagement => EnergyManagementDataLoadingStrategy(),
AnalyticsPageTab.occupancy => OccupancyDataLoadingStrategy(), AnalyticsPageTab.occupancy => OccupancyDataLoadingStrategy(),
AnalyticsPageTab.airQuality => AirQualityDataLoadingStrategy(),
}; };
} }
} }
@@ -68,9 +68,7 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
CommunityModel community, CommunityModel community,
SpaceModel child, SpaceModel child,
) { ) {
if (child.children.isNotEmpty) { return onSpaceSelected(context, community, child);
return onSpaceSelected(context, community, child);
}
} }
@override @override
@@ -48,7 +48,7 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
CommunityModel community, CommunityModel community,
SpaceModel child, SpaceModel child,
) { ) {
onSpaceSelected(context, community, child); return onSpaceSelected(context, community, child);
} }
@override @override
@@ -38,7 +38,7 @@ abstract final class EnergyManagementChartsHelper {
sideTitles: SideTitles( sideTitles: SideTitles(
showTitles: true, showTitles: true,
maxIncluded: false, maxIncluded: false,
minIncluded: true, minIncluded: false,
interval: leftTitlesInterval, interval: leftTitlesInterval,
reservedSize: 110, reservedSize: 110,
getTitlesWidget: (value, meta) => Padding( getTitlesWidget: (value, meta) => Padding(
@@ -50,7 +50,7 @@ abstract final class EnergyManagementChartsHelper {
value.formatNumberToKwh, value.formatNumberToKwh,
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
fontSize: 12, fontSize: 12,
color: ColorsManager.greyColor, color: ColorsManager.lightGreyColor,
), ),
), ),
), ),
@@ -170,7 +170,7 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
child: Text( child: Text(
month, month,
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.greyColor, color: ColorsManager.lightGreyColor,
fontSize: 11, fontSize: 11,
), ),
), ),
@@ -19,10 +19,12 @@ class EnergyConsumptionByPhasesChartBox extends StatelessWidget {
decoration: secondarySection, decoration: secondarySection,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
spacing: 20,
children: [ children: [
AnalyticsErrorWidget(state.errorMessage), AnalyticsErrorWidget(state.errorMessage),
EnergyConsumptionByPhasesTitle(isLoading: state.status == EnergyConsumptionByPhasesStatus.loading,), EnergyConsumptionByPhasesTitle(
isLoading: state.status == EnergyConsumptionByPhasesStatus.loading,
),
const SizedBox(height: 20),
Expanded( Expanded(
child: EnergyConsumptionByPhasesChart( child: EnergyConsumptionByPhasesChart(
energyData: state.chartData, energyData: state.chartData,
+5 -8
View File
@@ -51,13 +51,12 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
Future _fetchUserInfo(FetchUserInfo event, Emitter<HomeState> emit) async { Future _fetchUserInfo(FetchUserInfo event, Emitter<HomeState> emit) async {
try { try {
var uuid = var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
user = await HomeApi().fetchUserInfo(uuid); user = await HomeApi().fetchUserInfo(uuid);
if (user != null && user!.project != null) { if (user != null && user!.project != null) {
await ProjectManager.setProjectUUID(user!.project!.uuid); await ProjectManager.setProjectUUID(user!.project!.uuid);
// NavigationService.navigatorKey.currentContext!.read<SpaceTreeBloc>().add(InitialEvent()); NavigationService.navigatorKey.currentContext!.read<SpaceTreeBloc>().add(InitialEvent());
} }
add(FetchTermEvent()); add(FetchTermEvent());
add(FetchPolicyEvent()); add(FetchPolicyEvent());
@@ -89,12 +88,10 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
} }
} }
Future _confirmUserAgreement( Future _confirmUserAgreement(ConfirmUserAgreementEvent event, Emitter<HomeState> emit) async {
ConfirmUserAgreementEvent event, Emitter<HomeState> emit) async {
try { try {
emit(LoadingHome()); emit(LoadingHome());
var uuid = var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
policy = await HomeApi().confirmUserAgreements(uuid); policy = await HomeApi().confirmUserAgreements(uuid);
emit(PolicyAgreement()); emit(PolicyAgreement());
} catch (e) { } catch (e) {
@@ -158,7 +155,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
}, },
color: ColorsManager.primaryColor, color: ColorsManager.primaryColor,
), ),
// HomeItemModel( // HomeItemModel(
// title: 'Move in', // title: 'Move in',
// icon: Assets.moveinIcon, // icon: Assets.moveinIcon,
+6 -10
View File
@@ -24,7 +24,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
void initState() { void initState() {
super.initState(); super.initState();
final homeBloc = BlocProvider.of<HomeBloc>(context); final homeBloc = BlocProvider.of<HomeBloc>(context);
// homeBloc.add(const FetchUserInfo()); homeBloc.add(const FetchUserInfo());
} }
@override @override
@@ -38,10 +38,8 @@ class _HomeWebPageState extends State<HomeWebPage> {
child: BlocConsumer<HomeBloc, HomeState>( child: BlocConsumer<HomeBloc, HomeState>(
listener: (BuildContext context, state) { listener: (BuildContext context, state) {
if (state is HomeInitial) { if (state is HomeInitial) {
if (homeBloc.user!.hasAcceptedWebAgreement == false && if (homeBloc.user!.hasAcceptedWebAgreement == false && !_dialogShown) {
!_dialogShown) { _dialogShown = true; // Set the flag to true to indicate the dialog is showing.
_dialogShown =
true; // Set the flag to true to indicate the dialog is showing.
Future.delayed(const Duration(seconds: 1), () { Future.delayed(const Duration(seconds: 1), () {
showDialog( showDialog(
context: context, context: context,
@@ -56,7 +54,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
_dialogShown = false; _dialogShown = false;
if (v != null) { if (v != null) {
homeBloc.add(ConfirmUserAgreementEvent()); homeBloc.add(ConfirmUserAgreementEvent());
// homeBloc.add(const FetchUserInfo()); homeBloc.add(const FetchUserInfo());
} }
}); });
}); });
@@ -100,8 +98,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
width: size.width * 0.68, width: size.width * 0.68,
child: GridView.builder( child: GridView.builder(
itemCount: homeBloc.homeItems.length, itemCount: homeBloc.homeItems.length,
gridDelegate: gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // Adjust as needed. crossAxisCount: 3, // Adjust as needed.
crossAxisSpacing: 20.0, crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0, mainAxisSpacing: 20.0,
@@ -113,8 +110,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
active: homeBloc.homeItems[index].active!, active: homeBloc.homeItems[index].active!,
name: homeBloc.homeItems[index].title!, name: homeBloc.homeItems[index].title!,
img: homeBloc.homeItems[index].icon!, img: homeBloc.homeItems[index].icon!,
onTap: () => onTap: () => homeBloc.homeItems[index].onPress(context),
homeBloc.homeItems[index].onPress(context),
); );
}, },
), ),
@@ -21,8 +21,7 @@ import 'package:syncrow_web/services/space_mana_api.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart' as custom_action; import 'package:syncrow_web/utils/constants/action_enum.dart' as custom_action;
class SpaceManagementBloc class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementState> {
extends Bloc<SpaceManagementEvent, SpaceManagementState> {
final CommunitySpaceManagementApi _api; final CommunitySpaceManagementApi _api;
final ProductApi _productApi; final ProductApi _productApi;
final SpaceModelManagementApi _spaceModelApi; final SpaceModelManagementApi _spaceModelApi;
@@ -63,8 +62,7 @@ class SpaceManagementBloc
int page = 1; int page = 1;
while (hasNext) { while (hasNext) {
final spaceModels = await _spaceModelApi.listSpaceModels( final spaceModels = await _spaceModelApi.listSpaceModels(page: page, projectId: projectUuid);
page: page, projectId: projectUuid);
if (spaceModels.isNotEmpty) { if (spaceModels.isNotEmpty) {
allSpaceModels.addAll(spaceModels); allSpaceModels.addAll(spaceModels);
page++; page++;
@@ -77,29 +75,26 @@ class SpaceManagementBloc
await fetchTags(); await fetchTags();
emit(SpaceModelLoaded( emit(SpaceModelLoaded(
communities: state is SpaceManagementLoaded communities:
? (state as SpaceManagementLoaded).communities state is SpaceManagementLoaded ? (state as SpaceManagementLoaded).communities : [],
: [],
products: _cachedProducts ?? [], products: _cachedProducts ?? [],
spaceModels: List.from(_cachedSpaceModels ?? []), spaceModels: List.from(_cachedSpaceModels ?? []),
allTags: _cachedTags ?? [])); allTags: _cachedTags ?? []));
} }
void _deleteSpaceModelFromCache(DeleteSpaceModelFromCache event, void _deleteSpaceModelFromCache(
Emitter<SpaceManagementState> emit) async { DeleteSpaceModelFromCache event, Emitter<SpaceManagementState> emit) async {
if (_cachedSpaceModels != null) { if (_cachedSpaceModels != null) {
_cachedSpaceModels = _cachedSpaceModels! _cachedSpaceModels =
.where((model) => model.uuid != event.deletedUuid) _cachedSpaceModels!.where((model) => model.uuid != event.deletedUuid).toList();
.toList();
} else { } else {
_cachedSpaceModels = await fetchSpaceModels(); _cachedSpaceModels = await fetchSpaceModels();
} }
await fetchTags(); await fetchTags();
emit(SpaceModelLoaded( emit(SpaceModelLoaded(
communities: state is SpaceManagementLoaded communities:
? (state as SpaceManagementLoaded).communities state is SpaceManagementLoaded ? (state as SpaceManagementLoaded).communities : [],
: [],
products: _cachedProducts ?? [], products: _cachedProducts ?? [],
spaceModels: List.from(_cachedSpaceModels ?? []), spaceModels: List.from(_cachedSpaceModels ?? []),
allTags: _cachedTags ?? [])); allTags: _cachedTags ?? []));
@@ -127,8 +122,8 @@ class SpaceManagementBloc
int page = 1; int page = 1;
while (hasNext) { while (hasNext) {
final spaceModels = await _spaceModelApi.listSpaceModels( final spaceModels =
page: page, projectId: projectUuid); await _spaceModelApi.listSpaceModels(page: page, projectId: projectUuid);
if (spaceModels.isNotEmpty) { if (spaceModels.isNotEmpty) {
allSpaceModels.addAll(spaceModels); allSpaceModels.addAll(spaceModels);
page++; page++;
@@ -169,12 +164,10 @@ class SpaceManagementBloc
await fetchTags(); await fetchTags();
emit(SpaceManagementLoading()); emit(SpaceManagementLoading());
final success = await _api.updateCommunity( final success = await _api.updateCommunity(event.communityUuid, event.name, projectUuid);
event.communityUuid, event.name, projectUuid);
if (success) { if (success) {
if (previousState is SpaceManagementLoaded) { if (previousState is SpaceManagementLoaded) {
final updatedCommunities = final updatedCommunities = List<CommunityModel>.from(previousState.communities);
List<CommunityModel>.from(previousState.communities);
for (var community in updatedCommunities) { for (var community in updatedCommunities) {
if (community.uuid == event.communityUuid) { if (community.uuid == event.communityUuid) {
community.name = event.name; community.name = event.name;
@@ -219,8 +212,7 @@ class SpaceManagementBloc
} }
} }
Future<List<SpaceModel>> _fetchSpacesForCommunity( Future<List<SpaceModel>> _fetchSpacesForCommunity(String communityUuid) async {
String communityUuid) async {
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
return await _api.getSpaceHierarchy(communityUuid, projectUuid); return await _api.getSpaceHierarchy(communityUuid, projectUuid);
@@ -250,23 +242,20 @@ class SpaceManagementBloc
} }
} }
Future<void> _onBlankState( Future<void> _onBlankState(BlankStateEvent event, Emitter<SpaceManagementState> emit) async {
BlankStateEvent event, Emitter<SpaceManagementState> emit) async {
try { try {
final previousState = state; final previousState = state;
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
var spaceBloc = event.context.read<SpaceTreeBloc>(); var spaceBloc = event.context.read<SpaceTreeBloc>();
var spaceTreeState = event.context.read<SpaceTreeBloc>().state; var spaceTreeState = event.context.read<SpaceTreeBloc>().state;
List<CommunityModel> communities = List<CommunityModel> communities = await _waitForCommunityList(spaceBloc, spaceTreeState);
await _waitForCommunityList(spaceBloc, spaceTreeState);
await fetchSpaceModels(); await fetchSpaceModels();
// await fetchTags(); await fetchTags();
var prevSpaceModels = await fetchSpaceModels(); var prevSpaceModels = await fetchSpaceModels();
if (previousState is SpaceManagementLoaded || if (previousState is SpaceManagementLoaded || previousState is BlankState) {
previousState is BlankState) {
final prevCommunities = (previousState as dynamic).communities ?? []; final prevCommunities = (previousState as dynamic).communities ?? [];
emit(BlankState( emit(BlankState(
communities: List<CommunityModel>.from(prevCommunities), communities: List<CommunityModel>.from(prevCommunities),
@@ -297,8 +286,7 @@ class SpaceManagementBloc
_onloadProducts(); _onloadProducts();
await fetchTags(); await fetchTags();
// Wait until `communityList` is loaded // Wait until `communityList` is loaded
List<CommunityModel> communities = List<CommunityModel> communities = await _waitForCommunityList(spaceBloc, spaceTreeState);
await _waitForCommunityList(spaceBloc, spaceTreeState);
// Fetch space models after communities are available // Fetch space models after communities are available
final prevSpaceModels = await fetchSpaceModels(); final prevSpaceModels = await fetchSpaceModels();
@@ -322,9 +310,8 @@ class SpaceManagementBloc
final completer = Completer<List<CommunityModel>>(); final completer = Completer<List<CommunityModel>>();
final subscription = spaceBloc.stream.listen((state) { final subscription = spaceBloc.stream.listen((state) {
if (!completer.isCompleted && state.communityList.isNotEmpty) { if (!completer.isCompleted && state.communityList.isNotEmpty) {
completer.complete(state.searchQuery.isNotEmpty completer
? state.filteredCommunity .complete(state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList);
: state.communityList);
} }
}); });
try { try {
@@ -352,8 +339,7 @@ class SpaceManagementBloc
emit(SpaceManagementLoading()); emit(SpaceManagementLoading());
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
final success = final success = await _api.deleteCommunity(event.communityUuid, projectUuid);
await _api.deleteCommunity(event.communityUuid, projectUuid);
if (success) { if (success) {
// add(LoadCommunityAndSpacesEvent()); // add(LoadCommunityAndSpacesEvent());
} else { } else {
@@ -375,13 +361,12 @@ class SpaceManagementBloc
try { try {
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
await fetchTags(); await fetchTags();
CommunityModel? newCommunity = await _api.createCommunity( CommunityModel? newCommunity =
event.name, event.description, projectUuid); await _api.createCommunity(event.name, event.description, projectUuid);
var prevSpaceModels = await fetchSpaceModels(); var prevSpaceModels = await fetchSpaceModels();
if (newCommunity != null) { if (newCommunity != null) {
if (previousState is SpaceManagementLoaded || if (previousState is SpaceManagementLoaded || previousState is BlankState) {
previousState is BlankState) {
final prevCommunities = List<CommunityModel>.from( final prevCommunities = List<CommunityModel>.from(
(previousState as dynamic).communities, (previousState as dynamic).communities,
); );
@@ -474,15 +459,12 @@ class SpaceManagementBloc
try { try {
final spaceTreeState = event.context.read<SpaceTreeBloc>().state; final spaceTreeState = event.context.read<SpaceTreeBloc>().state;
final updatedSpaces = await saveSpacesHierarchically( final updatedSpaces =
event.context, event.spaces, event.communityUuid); await saveSpacesHierarchically(event.context, event.spaces, event.communityUuid);
final allSpaces = await _fetchSpacesForCommunity(event.communityUuid); final allSpaces = await _fetchSpacesForCommunity(event.communityUuid);
// emit(SpaceCreationSuccess(spaces: updatedSpaces)); emit(SpaceCreationSuccess(spaces: updatedSpaces));
// updatedSpaces.forEach(
// (element) => element.uuid,
// );
// final lastUpdatedSpaced = updatedSpaces..addAll(allSpaces);
if (previousState is SpaceManagementLoaded) { if (previousState is SpaceManagementLoaded) {
await _updateLoadedState( await _updateLoadedState(
spaceTreeState, spaceTreeState,
@@ -493,7 +475,7 @@ class SpaceManagementBloc
); );
} }
} catch (e) { } catch (e) {
// emit(SpaceManagementError('Error saving spaces: $e')); emit(SpaceManagementError('Error saving spaces: $e'));
if (previousState is SpaceManagementLoaded) { if (previousState is SpaceManagementLoaded) {
emit(previousState); emit(previousState);
@@ -533,15 +515,13 @@ class SpaceManagementBloc
return; return;
} }
} }
emit(previousState);
} catch (e, stackTrace) { } catch (e, stackTrace) {
emit(previousState); rethrow;
// rethrow;
} }
} }
Future<List<SpaceModel>> saveSpacesHierarchically(BuildContext context, Future<List<SpaceModel>> saveSpacesHierarchically(
List<SpaceModel> spaces, String communityUuid) async { BuildContext context, List<SpaceModel> spaces, String communityUuid) async {
final orderedSpaces = flattenHierarchy(spaces); final orderedSpaces = flattenHierarchy(spaces);
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
@@ -554,14 +534,6 @@ class SpaceManagementBloc
selectedCommunity = filteredCommunities.firstWhere( selectedCommunity = filteredCommunities.firstWhere(
(community) => community.uuid == communityUuid, (community) => community.uuid == communityUuid,
orElse: () => CommunityModel(
uuid: '',
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
name: '',
description: '',
spaces: spaces,
),
); );
} catch (e) { } catch (e) {
return []; return [];
@@ -576,7 +548,9 @@ class SpaceManagementBloc
if (parent.uuid != null) { if (parent.uuid != null) {
await _api.deleteSpace(communityUuid, parent.uuid!, projectUuid); await _api.deleteSpace(communityUuid, parent.uuid!, projectUuid);
} }
} catch (e) {} } catch (e) {
rethrow;
}
} }
orderedSpaces.removeWhere((space) => parentsToDelete.contains(space)); orderedSpaces.removeWhere((space) => parentsToDelete.contains(space));
@@ -590,7 +564,7 @@ class SpaceManagementBloc
if (matchedSpaces.isEmpty) continue; if (matchedSpaces.isEmpty) continue;
final prevSpace = matchedSpaces.elementAtOrNull(0); final prevSpace = matchedSpaces[0];
final List<UpdateSubspaceTemplateModel> subspaceUpdates = []; final List<UpdateSubspaceTemplateModel> subspaceUpdates = [];
final List<SubspaceModel>? prevSubspaces = prevSpace?.subspaces; final List<SubspaceModel>? prevSubspaces = prevSpace?.subspaces;
@@ -601,19 +575,17 @@ class SpaceManagementBloc
if (prevSubspaces != null || newSubspaces != null) { if (prevSubspaces != null || newSubspaces != null) {
if (prevSubspaces != null && newSubspaces != null) { if (prevSubspaces != null && newSubspaces != null) {
for (var prevSubspace in prevSubspaces) { for (var prevSubspace in prevSubspaces) {
final existsInNew = newSubspaces final existsInNew =
.any((subspace) => subspace.uuid == prevSubspace.uuid); newSubspaces.any((subspace) => subspace.uuid == prevSubspace.uuid);
if (!existsInNew) { if (!existsInNew) {
subspaceUpdates.add(UpdateSubspaceTemplateModel( subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: custom_action.Action.delete, action: custom_action.Action.delete, uuid: prevSubspace.uuid));
uuid: prevSubspace.uuid));
} }
} }
} else if (prevSubspaces != null && newSubspaces == null) { } else if (prevSubspaces != null && newSubspaces == null) {
for (var prevSubspace in prevSubspaces) { for (var prevSubspace in prevSubspaces) {
subspaceUpdates.add(UpdateSubspaceTemplateModel( subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: custom_action.Action.delete, action: custom_action.Action.delete, uuid: prevSubspace.uuid));
uuid: prevSubspace.uuid));
} }
} }
@@ -641,9 +613,7 @@ class SpaceManagementBloc
} }
if (prevSubspaces != null && newSubspaces != null) { if (prevSubspaces != null && newSubspaces != null) {
final newSubspaceMap = { final newSubspaceMap = {for (var subspace in newSubspaces) subspace.uuid: subspace};
for (var subspace in newSubspaces) subspace.uuid: subspace
};
for (var prevSubspace in prevSubspaces) { for (var prevSubspace in prevSubspaces) {
final newSubspace = newSubspaceMap[prevSubspace.uuid]; final newSubspace = newSubspaceMap[prevSubspace.uuid];
@@ -669,10 +639,9 @@ class SpaceManagementBloc
isPrivate: space.isPrivate, isPrivate: space.isPrivate,
position: space.position, position: space.position,
icon: space.icon, icon: space.icon,
subspaces: space.subspaces, subspaces: subspaceUpdates,
// subspaceUpdates, tags: tagUpdates,
tags: space.tags, direction: space.incomingConnection?.direction,
// tagUpdates,
spaceModelUuid: space.spaceModel?.uuid, spaceModelUuid: space.spaceModel?.uuid,
projectId: projectUuid); projectId: projectUuid);
} else { } else {
@@ -682,10 +651,8 @@ class SpaceManagementBloc
: []; : [];
var createSubspaceBodyModels = space.subspaces?.map((subspace) { var createSubspaceBodyModels = space.subspaces?.map((subspace) {
final tagBodyModels = subspace.tags final tagBodyModels =
?.map((tag) => tag.toCreateTagBodyModel()) subspace.tags?.map((tag) => tag.toCreateTagBodyModel()).toList() ?? [];
.toList() ??
[];
return CreateSubspaceModel() return CreateSubspaceModel()
..subspaceName = subspace.subspaceName ..subspaceName = subspace.subspaceName
..tags = tagBodyModels; ..tags = tagBodyModels;
@@ -704,6 +671,7 @@ class SpaceManagementBloc
isPrivate: space.isPrivate, isPrivate: space.isPrivate,
position: space.position, position: space.position,
icon: space.icon, icon: space.icon,
direction: space.incomingConnection?.direction,
spaceModelUuid: space.spaceModel?.uuid, spaceModelUuid: space.spaceModel?.uuid,
tags: tagBodyModels, tags: tagBodyModels,
subspaces: createSubspaceBodyModels, subspaces: createSubspaceBodyModels,
@@ -711,8 +679,7 @@ class SpaceManagementBloc
space.uuid = response?.uuid; space.uuid = response?.uuid;
} }
} catch (e) { } catch (e) {
return []; rethrow; // Stop further execution on failure
// Stop further execution on failure
} }
} }
return spaces; return spaces;
@@ -743,8 +710,7 @@ class SpaceManagementBloc
return result.toList(); // Convert back to a list return result.toList(); // Convert back to a list
} }
void _onLoadSpaceModel( void _onLoadSpaceModel(SpaceModelLoadEvent event, Emitter<SpaceManagementState> emit) async {
SpaceModelLoadEvent event, Emitter<SpaceManagementState> emit) async {
emit(SpaceManagementLoading()); emit(SpaceManagementLoading());
try { try {
@@ -791,17 +757,14 @@ class SpaceManagementBloc
// Case 1: Tags deleted // Case 1: Tags deleted
if (prevTags != null && newTags != null) { if (prevTags != null && newTags != null) {
for (var prevTag in prevTags) { for (var prevTag in prevTags) {
final existsInNew = final existsInNew = newTags.any((newTag) => newTag.uuid == prevTag.uuid);
newTags.any((newTag) => newTag.uuid == prevTag.uuid);
if (!existsInNew) { if (!existsInNew) {
tagUpdates.add(TagModelUpdate( tagUpdates.add(TagModelUpdate(action: custom_action.Action.delete, uuid: prevTag.uuid));
action: custom_action.Action.delete, uuid: prevTag.uuid));
} }
} }
} else if (prevTags != null && newTags == null) { } else if (prevTags != null && newTags == null) {
for (var prevTag in prevTags) { for (var prevTag in prevTags) {
tagUpdates.add(TagModelUpdate( tagUpdates.add(TagModelUpdate(action: custom_action.Action.delete, uuid: prevTag.uuid));
action: custom_action.Action.delete, uuid: prevTag.uuid));
} }
} }
@@ -844,16 +807,15 @@ class SpaceManagementBloc
return tagUpdates; return tagUpdates;
} }
List<SpaceModel> findMatchingSpaces( List<SpaceModel> findMatchingSpaces(List<SpaceModel> spaces, String targetUuid) {
List<SpaceModel> spaces, String targetUuid) {
List<SpaceModel> matched = []; List<SpaceModel> matched = [];
for (var space in spaces) { for (var space in spaces) {
if (space.uuid == targetUuid) { if (space.uuid == targetUuid) {
matched.add(space); matched.add(space);
} }
matched.addAll(findMatchingSpaces( matched
space.children, targetUuid)); // Recursively search in children .addAll(findMatchingSpaces(space.children, targetUuid)); // Recursively search in children
} }
return matched; return matched;
@@ -3,26 +3,23 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model
class Connection { class Connection {
final SpaceModel startSpace; final SpaceModel startSpace;
final SpaceModel endSpace; final SpaceModel endSpace;
final String direction;
Connection({ Connection({required this.startSpace, required this.endSpace, required this.direction});
required this.startSpace,
required this.endSpace,
});
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return { return {
'startUuid': startSpace.uuid ?? 'startUuid': startSpace.uuid ?? 'unsaved-start-space-${startSpace.name}', // Fallback for unsaved spaces
'unsaved-start-space-${startSpace.name}', // Fallback for unsaved spaces 'endUuid': endSpace.uuid ?? 'unsaved-end-space-${endSpace.name}', // Fallback for unsaved spaces
'endUuid': endSpace.uuid ?? 'direction': direction,
'unsaved-end-space-${endSpace.name}', // Fallback for unsaved spaces
}; };
} }
static Connection fromMap( static Connection fromMap(Map<String, dynamic> map, Map<String, SpaceModel> spaces) {
Map<String, dynamic> map, Map<String, SpaceModel> spaces) {
return Connection( return Connection(
startSpace: spaces[map['startUuid']]!, startSpace: spaces[map['startUuid']]!,
endSpace: spaces[map['endUuid']]!, endSpace: spaces[map['endUuid']]!,
direction: map['direction'],
); );
} }
} }
@@ -1,7 +1,5 @@
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'selected_product_model.dart';
class ProductModel { class ProductModel {
final String uuid; final String uuid;
final String catName; final String catName;
@@ -40,15 +38,6 @@ class ProductModel {
}; };
} }
SelectedProduct toSelectedProduct(int count) {
return SelectedProduct(
productId: uuid,
count: count,
productName: name!,
product: this,
);
}
static String _mapIconToProduct(String prodType) { static String _mapIconToProduct(String prodType) {
const iconMapping = { const iconMapping = {
'1G': Assets.Gang1SwitchIcon, '1G': Assets.Gang1SwitchIcon,
@@ -101,7 +101,7 @@ class SpaceModel {
spaceModel: json['spaceModel'] != null spaceModel: json['spaceModel'] != null
? SpaceTemplateModel.fromJson(json['spaceModel']) ? SpaceTemplateModel.fromJson(json['spaceModel'])
: null, : null,
tags: (json['productAllocations'] as List<dynamic>?) tags: (json['tags'] as List<dynamic>?)
?.where((item) => item is Map<String, dynamic>) // Validate type ?.where((item) => item is Map<String, dynamic>) // Validate type
.map((item) => Tag.fromJson(item as Map<String, dynamic>)) .map((item) => Tag.fromJson(item as Map<String, dynamic>))
.toList() ?? .toList() ??
@@ -116,6 +116,7 @@ class SpaceModel {
instance.incomingConnection = Connection( instance.incomingConnection = Connection(
startSpace: instance.parent ?? instance, // Parent space startSpace: instance.parent ?? instance, // Parent space
endSpace: instance, // This space instance endSpace: instance, // This space instance
direction: conn['direction'],
); );
} }
@@ -27,7 +27,7 @@ class SubspaceModel {
subspaceName: json['subspaceName'] ?? '', subspaceName: json['subspaceName'] ?? '',
disabled: json['disabled'] ?? false, disabled: json['disabled'] ?? false,
internalId: internalId, internalId: internalId,
tags: (json['productAllocations'] as List<dynamic>?) tags: (json['tags'] as List<dynamic>?)
?.map((item) => Tag.fromJson(item)) ?.map((item) => Tag.fromJson(item))
.toList() ?? .toList() ??
[], [],
@@ -36,7 +36,7 @@ class SubspaceModel {
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
if (uuid != null) 'uuid': uuid, 'uuid': uuid,
'subspaceName': subspaceName, 'subspaceName': subspaceName,
'disabled': disabled, 'disabled': disabled,
'tags': tags?.map((e) => e.toJson()).toList() ?? [], 'tags': tags?.map((e) => e.toJson()).toList() ?? [],
@@ -23,13 +23,10 @@ class Tag extends BaseTag {
final String internalId = json['internalId'] ?? const Uuid().v4(); final String internalId = json['internalId'] ?? const Uuid().v4();
return Tag( return Tag(
//TODO:insure UUId for tag or prodAlloc uuid: json['uuid'] ?? '',
uuid: json['name'] != null ? json['uuid'] : json['tag']?['uuid'] ?? '',
internalId: internalId, internalId: internalId,
tag: json['name'] ?? json['tag']?['name'] ?? '', tag: json['name'] ?? '',
product: json['product'] != null product: json['product'] != null ? ProductModel.fromMap(json['product']) : null,
? ProductModel.fromMap(json['product'])
: null,
); );
} }
@@ -52,10 +49,9 @@ class Tag extends BaseTag {
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
if (uuid != null) 'uuid': uuid, 'uuid': uuid,
'name': tag, 'tag': tag,
'productUuid': product?.uuid, 'product': product?.toMap(),
// .toMap(),
}; };
} }
} }
@@ -67,8 +67,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void initState() { void initState() {
super.initState(); super.initState();
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections = connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces(); _adjustCanvasSizeForSpaces();
_nameController = TextEditingController( _nameController = TextEditingController(
text: widget.selectedCommunity?.name ?? '', text: widget.selectedCommunity?.name ?? '',
@@ -97,15 +96,13 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
if (oldWidget.spaces != widget.spaces) { if (oldWidget.spaces != widget.spaces) {
setState(() { setState(() {
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections = connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces(); _adjustCanvasSizeForSpaces();
realignTree(); realignTree();
}); });
} }
if (widget.selectedSpace != oldWidget.selectedSpace && if (widget.selectedSpace != oldWidget.selectedSpace && widget.selectedSpace != null) {
widget.selectedSpace != null) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_moveToSpace(widget.selectedSpace!); _moveToSpace(widget.selectedSpace!);
}); });
@@ -188,8 +185,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
connection, widget.selectedSpace) connection, widget.selectedSpace)
? 1.0 ? 1.0
: 0.3, // Adjust opacity : 0.3, // Adjust opacity
child: CustomPaint( child: CustomPaint(painter: CurvedLinePainter([connection])),
painter: CurvedLinePainter([connection])),
), ),
for (var entry in spaces.asMap().entries) for (var entry in spaces.asMap().entries)
if (entry.value.status != SpaceStatus.deleted && if (entry.value.status != SpaceStatus.deleted &&
@@ -199,11 +195,11 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
top: entry.value.position.dy, top: entry.value.position.dy,
child: SpaceCardWidget( child: SpaceCardWidget(
index: entry.key, index: entry.key,
onButtonTap: (int index, Offset newPosition) { onButtonTap: (int index, Offset newPosition, String direction) {
_showCreateSpaceDialog(screenSize, _showCreateSpaceDialog(screenSize,
position: position: spaces[index].position + newPosition,
spaces[index].position + newPosition,
parentIndex: index, parentIndex: index,
direction: direction,
projectTags: widget.projectTags); projectTags: widget.projectTags);
}, },
position: entry.value.position, position: entry.value.position,
@@ -214,9 +210,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
_updateNodePosition(entry.value, newPosition); _updateNodePosition(entry.value, newPosition);
}, },
buildSpaceContainer: (int index) { buildSpaceContainer: (int index) {
final bool isHighlighted = final bool isHighlighted = SpaceHelper.isHighlightedSpace(
SpaceHelper.isHighlightedSpace( spaces[index], widget.selectedSpace);
spaces[index], widget.selectedSpace);
return Opacity( return Opacity(
opacity: isHighlighted ? 1.0 : 0.3, opacity: isHighlighted ? 1.0 : 0.3,
@@ -294,6 +289,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void _showCreateSpaceDialog(Size screenSize, void _showCreateSpaceDialog(Size screenSize,
{Offset? position, {Offset? position,
int? parentIndex, int? parentIndex,
String? direction,
double? canvasWidth, double? canvasWidth,
double? canvasHeight, double? canvasHeight,
required List<Tag> projectTags}) { required List<Tag> projectTags}) {
@@ -303,25 +299,19 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
return CreateSpaceDialog( return CreateSpaceDialog(
products: widget.products, products: widget.products,
spaceModels: widget.spaceModels, spaceModels: widget.spaceModels,
allTags: allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
parentSpace: parentIndex != null ? spaces[parentIndex] : null, parentSpace: parentIndex != null ? spaces[parentIndex] : null,
projectTags: projectTags, projectTags: projectTags,
onCreateSpace: (String name, onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts,
String icon, SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) {
List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel,
List<SubspaceModel>? subspaces,
List<Tag>? tags) {
setState(() { setState(() {
// Set the first space in the center or use passed position // Set the first space in the center or use passed position
Offset newPosition; Offset newPosition;
if (parentIndex != null) { if (parentIndex != null) {
newPosition = getBalancedChildPosition(
spaces[parentIndex]); // Ensure balanced position
} else {
newPosition = newPosition =
position ?? ConnectionHelper.getCenterPosition(screenSize); getBalancedChildPosition(spaces[parentIndex]); // Ensure balanced position
} else {
newPosition = position ?? ConnectionHelper.getCenterPosition(screenSize);
} }
SpaceModel newSpace = SpaceModel( SpaceModel newSpace = SpaceModel(
@@ -335,13 +325,14 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
subspaces: subspaces, subspaces: subspaces,
tags: tags); tags: tags);
if (parentIndex != null) { if (parentIndex != null && direction != null) {
SpaceModel parentSpace = spaces[parentIndex]; SpaceModel parentSpace = spaces[parentIndex];
parentSpace.internalId = spaces[parentIndex].internalId; parentSpace.internalId = spaces[parentIndex].internalId;
newSpace.parent = parentSpace; newSpace.parent = parentSpace;
final newConnection = Connection( final newConnection = Connection(
startSpace: parentSpace, startSpace: parentSpace,
endSpace: newSpace, endSpace: newSpace,
direction: direction,
); );
connections.add(newConnection); connections.add(newConnection);
newSpace.incomingConnection = newConnection; newSpace.incomingConnection = newConnection;
@@ -369,21 +360,16 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
name: widget.selectedSpace!.name, name: widget.selectedSpace!.name,
icon: widget.selectedSpace!.icon, icon: widget.selectedSpace!.icon,
projectTags: widget.projectTags, projectTags: widget.projectTags,
parentSpace: SpaceHelper.findSpaceByInternalId( parentSpace:
widget.selectedSpace?.parent?.internalId, spaces), SpaceHelper.findSpaceByInternalId(widget.selectedSpace?.parent?.internalId, spaces),
editSpace: widget.selectedSpace, editSpace: widget.selectedSpace,
currentSpaceModel: widget.selectedSpace?.spaceModel, currentSpaceModel: widget.selectedSpace?.spaceModel,
tags: widget.selectedSpace?.tags, tags: widget.selectedSpace?.tags,
subspaces: widget.selectedSpace?.subspaces, subspaces: widget.selectedSpace?.subspaces,
isEdit: true, isEdit: true,
allTags: TagHelper.getAllTagValues( allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
widget.communities, widget.spaceModels), onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts,
onCreateSpace: (String name, SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) {
String icon,
List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel,
List<SubspaceModel>? subspaces,
List<Tag>? tags) {
setState(() { setState(() {
// Update the space's properties // Update the space's properties
widget.selectedSpace!.name = name; widget.selectedSpace!.name = name;
@@ -393,8 +379,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
widget.selectedSpace!.tags = tags; widget.selectedSpace!.tags = tags;
if (widget.selectedSpace!.status != SpaceStatus.newSpace) { if (widget.selectedSpace!.status != SpaceStatus.newSpace) {
widget.selectedSpace!.status = widget.selectedSpace!.status = SpaceStatus.modified; // Mark as modified
SpaceStatus.modified; // Mark as modified
} }
for (var space in spaces) { for (var space in spaces) {
@@ -425,8 +410,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
Map<String, SpaceModel> idToSpace = {}; Map<String, SpaceModel> idToSpace = {};
void flatten(SpaceModel space) { void flatten(SpaceModel space) {
if (space.status == SpaceStatus.deleted || if (space.status == SpaceStatus.deleted || space.status == SpaceStatus.parentDeleted) {
space.status == SpaceStatus.parentDeleted) {
return; return;
} }
result.add(space); result.add(space);
@@ -463,6 +447,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
Connection( Connection(
startSpace: parent, startSpace: parent,
endSpace: child, endSpace: child,
direction: "down",
), ),
); );
@@ -547,16 +532,13 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void _selectSpace(BuildContext context, SpaceModel space) { void _selectSpace(BuildContext context, SpaceModel space) {
context.read<SpaceManagementBloc>().add( context.read<SpaceManagementBloc>().add(
SelectSpaceEvent( SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: space),
selectedCommunity: widget.selectedCommunity,
selectedSpace: space),
); );
} }
void _deselectSpace(BuildContext context) { void _deselectSpace(BuildContext context) {
context.read<SpaceManagementBloc>().add( context.read<SpaceManagementBloc>().add(
SelectSpaceEvent( SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: null),
selectedCommunity: widget.selectedCommunity, selectedSpace: null),
); );
} }
@@ -726,8 +708,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
SpaceModel duplicated = _deepCloneSpaceTree(space, parent: parent); SpaceModel duplicated = _deepCloneSpaceTree(space, parent: parent);
duplicated.position = duplicated.position = Offset(space.position.dx + 300, space.position.dy + 100);
Offset(space.position.dx + 300, space.position.dy + 100);
List<SpaceModel> duplicatedSubtree = []; List<SpaceModel> duplicatedSubtree = [];
void collectSubtree(SpaceModel node) { void collectSubtree(SpaceModel node) {
duplicatedSubtree.add(node); duplicatedSubtree.add(node);
@@ -745,6 +726,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
final newConnection = Connection( final newConnection = Connection(
startSpace: parent, startSpace: parent,
endSpace: duplicated, endSpace: duplicated,
direction: "down",
); );
connections.add(newConnection); connections.add(newConnection);
duplicated.incomingConnection = newConnection; duplicated.incomingConnection = newConnection;
@@ -757,8 +739,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
} }
SpaceModel _deepCloneSpaceTree(SpaceModel original, {SpaceModel? parent}) { SpaceModel _deepCloneSpaceTree(SpaceModel original, {SpaceModel? parent}) {
final duplicatedName = final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces);
SpaceHelper.generateUniqueSpaceName(original.name, spaces);
final newSpace = SpaceModel( final newSpace = SpaceModel(
name: duplicatedName, name: duplicatedName,
@@ -780,6 +761,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
final newConnection = Connection( final newConnection = Connection(
startSpace: newSpace, startSpace: newSpace,
endSpace: duplicatedChild, endSpace: duplicatedChild,
direction: "down",
); );
connections.add(newConnection); connections.add(newConnection);
@@ -1,101 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import '../../../../../common/edit_chip.dart';
import '../../../../../utils/color_manager.dart';
import '../../../helper/tag_helper.dart';
import '../../../space_model/widgets/button_content_widget.dart';
import '../../model/subspace_model.dart';
import '../../model/tag.dart';
class DevicesPartWidget extends StatelessWidget {
const DevicesPartWidget({
super.key,
required this.tags,
required this.subspaces,
required this.screenWidth,
required this.onEditChip,
required this.onTextButtonPressed,
required this.isTagsAndSubspaceModelDisabled,
});
final bool isTagsAndSubspaceModelDisabled;
final void Function() onEditChip;
final void Function() onTextButtonPressed;
final double screenWidth;
final List<Tag>? tags;
final List<SubspaceModel>? subspaces;
@override
Widget build(BuildContext context) {
return Column(
children: [
(tags?.isNotEmpty == true ||
subspaces?.any(
(subspace) => subspace.tags?.isNotEmpty == true) ==
true)
? SizedBox(
width: screenWidth * 0.25,
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: ColorsManager.textFieldGreyColor,
borderRadius: BorderRadius.circular(15),
border: Border.all(
color: ColorsManager.textFieldGreyColor,
width: 3.0, // Border width
),
),
child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: [
// Combine tags from spaceModel and subspaces
...TagHelper.groupTags([
...?tags,
...?subspaces?.expand((subspace) => subspace.tags ?? [])
]).entries.map(
(entry) => Chip(
avatar: SizedBox(
width: 24,
height: 24,
child: SvgPicture.asset(
entry.key.icon ?? 'assets/icons/gateway.svg',
fit: BoxFit.contain,
),
),
label: Text(
'x${entry.value}', // Show count
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.spaceColor),
),
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: const BorderSide(
color: ColorsManager.spaceColor,
),
),
),
),
EditChip(onTap: onEditChip)
],
),
),
)
: TextButton(
onPressed: onTextButtonPressed,
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
),
child: ButtonContentWidget(
icon: Icons.add,
label: 'Add Devices',
disabled: isTagsAndSubspaceModelDisabled,
),
)
],
);
}
}
@@ -1,65 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import '../../../../../utils/color_manager.dart';
import '../../../../../utils/constants/assets.dart';
class IconChoosePartWidget extends StatelessWidget {
const IconChoosePartWidget({
super.key,
required this.selectedIcon,
required this.showIconSelection,
required this.screenWidth,
});
final double screenWidth;
final String selectedIcon;
final void Function() showIconSelection;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 50),
Stack(
alignment: Alignment.center,
children: [
Container(
width: screenWidth * 0.1,
height: screenWidth * 0.1,
decoration: const BoxDecoration(
color: ColorsManager.boxColor,
shape: BoxShape.circle,
),
),
SvgPicture.asset(
selectedIcon,
width: screenWidth * 0.04,
height: screenWidth * 0.04,
),
Positioned(
top: 20,
right: 20,
child: InkWell(
onTap: showIconSelection,
child: Container(
width: 24,
height: 24,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: SvgPicture.asset(
Assets.iconEdit,
width: 16,
height: 16,
),
),
),
),
],
),
],
);
}
}
@@ -1,89 +0,0 @@
import 'package:flutter/material.dart';
import '../../../../../utils/color_manager.dart';
import '../../../../../utils/constants/assets.dart';
import '../../../space_model/models/space_template_model.dart';
import '../../../space_model/widgets/button_content_widget.dart';
class SpaceModelLinkingWidget extends StatelessWidget {
const SpaceModelLinkingWidget({
super.key,
required this.onDeleted,
required this.onPressed,
required this.screenWidth,
required this.selectedSpaceModel,
required this.isSpaceModelDisabled,
});
final bool isSpaceModelDisabled;
final void Function()? onDeleted;
final void Function()? onPressed;
final double screenWidth;
final SpaceTemplateModel? selectedSpaceModel;
@override
Widget build(BuildContext context) {
return Column(
children: [
selectedSpaceModel == null
? TextButton(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
),
onPressed: onPressed,
child: ButtonContentWidget(
svgAssets: Assets.link,
label: 'Link a space model',
disabled: isSpaceModelDisabled,
),
)
: Container(
width: screenWidth * 0.25,
padding: const EdgeInsets.symmetric(
vertical: 10.0, horizontal: 16.0),
decoration: BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(10),
),
child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: [
Chip(
label: Text(
selectedSpaceModel?.modelName ?? '',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: ColorsManager.spaceColor),
),
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: const BorderSide(
color: ColorsManager.transparentColor,
width: 0,
),
),
deleteIcon: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.lightGrayColor,
width: 1.5,
),
),
child: const Icon(
Icons.close,
size: 16,
color: ColorsManager.lightGrayColor,
),
),
onDeleted: onDeleted),
],
),
),
],
);
}
}
@@ -1,82 +0,0 @@
import 'package:flutter/material.dart';
import '../../../../../utils/color_manager.dart';
class SpaceNameTextfieldWidget extends StatelessWidget {
SpaceNameTextfieldWidget({
super.key,
required this.isNameFieldExist,
required this.isNameFieldInvalid,
required this.onChange,
required this.screenWidth,
required this.nameController,
});
TextEditingController nameController;
final void Function(String value) onChange;
final double screenWidth;
bool isNameFieldExist;
bool isNameFieldInvalid;
@override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(
width: screenWidth * 0.25,
child: TextField(
controller: nameController,
onChanged: onChange,
style: Theme.of(context).textTheme.bodyMedium,
decoration: InputDecoration(
hintText: 'Please enter the name',
hintStyle: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: ColorsManager.lightGrayColor),
filled: true,
fillColor: ColorsManager.boxColor,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(
color: isNameFieldInvalid || isNameFieldExist
? ColorsManager.red
: ColorsManager.boxColor,
width: 1.5,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(
color: ColorsManager.boxColor,
width: 1.5,
),
),
),
),
),
if (isNameFieldInvalid)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
'*Space name should not be empty.',
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.red),
),
),
if (isNameFieldExist)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
'*Name already exist',
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.red),
),
),
],
);
}
}
@@ -1,101 +0,0 @@
import 'package:flutter/material.dart';
import '../../../../../common/edit_chip.dart';
import '../../../../../utils/color_manager.dart';
import '../../../space_model/widgets/button_content_widget.dart';
import '../../../space_model/widgets/subspace_name_label_widget.dart';
import '../../model/subspace_model.dart';
class SubSpacePartWidget extends StatefulWidget {
SubSpacePartWidget({
super.key,
required this.subspaces,
required this.onPressed,
required this.isTagsAndSubspaceModelDisabled,
required this.screenWidth,
required this.editChipOnTap,
});
double screenWidth;
bool isTagsAndSubspaceModelDisabled;
final void Function() editChipOnTap;
List<SubspaceModel>? subspaces;
final void Function() onPressed;
@override
State<SubSpacePartWidget> createState() => _SubSpacePartWidgetState();
}
class _SubSpacePartWidgetState extends State<SubSpacePartWidget> {
@override
Widget build(BuildContext context) {
return Column(
children: [
widget.subspaces == null || widget.subspaces!.isEmpty
? TextButton(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
overlayColor: ColorsManager.transparentColor,
),
onPressed: widget.onPressed,
child: ButtonContentWidget(
icon: Icons.add,
label: 'Create Sub Spaces',
disabled: widget.isTagsAndSubspaceModelDisabled,
),
)
: SizedBox(
width: widget.screenWidth * 0.25,
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: ColorsManager.textFieldGreyColor,
borderRadius: BorderRadius.circular(15),
border: Border.all(
color: ColorsManager.textFieldGreyColor,
width: 3.0, // Border width
),
),
child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: [
if (widget.subspaces != null)
...widget.subspaces!.map((subspace) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SubspaceNameDisplayWidget(
text: subspace.subspaceName,
validateName: (updatedName) {
bool nameExists = widget.subspaces!.any((s) {
bool isSameId =
s.internalId == subspace.internalId;
bool isSameName =
s.subspaceName.trim().toLowerCase() ==
updatedName.trim().toLowerCase();
return !isSameId && isSameName;
});
return !nameExists;
},
onNameChanged: (updatedName) {
setState(() {
subspace.subspaceName = updatedName;
});
},
),
],
);
}),
EditChip(
onTap: widget.editChipOnTap,
)
],
),
),
)
],
);
}
}
@@ -30,13 +30,28 @@ class CurvedLinePainter extends CustomPainter {
Offset end = connection.endSpace.position + Offset end = connection.endSpace.position +
const Offset(75, 0); // Center top of end space const Offset(75, 0); // Center top of end space
// Curved line for down connections if (connection.direction == 'down') {
final controlPoint = Offset((start.dx + end.dx) / 2, start.dy + 50); // Curved line for down connections
final path = Path() final controlPoint = Offset((start.dx + end.dx) / 2, start.dy + 50);
..moveTo(start.dx, start.dy) final path = Path()
..quadraticBezierTo(controlPoint.dx, controlPoint.dy, end.dx, end.dy); ..moveTo(start.dx, start.dy)
canvas.drawPath(path, paint); ..quadraticBezierTo(controlPoint.dx, controlPoint.dy, end.dx, end.dy);
canvas.drawPath(path, paint);
} else if (connection.direction == 'right') {
start = connection.startSpace.position +
const Offset(150, 30); // Right center
end = connection.endSpace.position + const Offset(0, 30); // Left center
canvas.drawLine(start, end, paint);
} else if (connection.direction == 'left') {
start =
connection.startSpace.position + const Offset(0, 30); // Left center
end = connection.endSpace.position +
const Offset(150, 30); // Right center
canvas.drawLine(start, end, paint);
}
final dotPaint = Paint()..color = ColorsManager.blackColor; final dotPaint = Paint()..color = ColorsManager.blackColor;
canvas.drawCircle(start, 5, dotPaint); // Start dot canvas.drawCircle(start, 5, dotPaint); // Start dot
canvas.drawCircle(end, 5, dotPaint); // End dot canvas.drawCircle(end, 5, dotPaint); // End dot
@@ -1,4 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/common/edit_chip.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_widget.dart';
@@ -7,11 +9,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_pr
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/devices_part_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/icon_choose_part_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/space_model_linking_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/space_name_textfield_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/sub_space_part_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart';
@@ -19,6 +16,8 @@ import 'package:syncrow_web/pages/spaces_management/helper/space_helper.dart';
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/constants/space_icon_const.dart'; import 'package:syncrow_web/utils/constants/space_icon_const.dart';
@@ -83,10 +82,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
super.initState(); super.initState();
selectedIcon = widget.icon ?? Assets.location; selectedIcon = widget.icon ?? Assets.location;
nameController = TextEditingController(text: widget.name ?? ''); nameController = TextEditingController(text: widget.name ?? '');
selectedProducts = selectedProducts = widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
widget.selectedProducts.isNotEmpty ? widget.selectedProducts : []; isOkButtonEnabled = enteredName.isNotEmpty || nameController.text.isNotEmpty;
isOkButtonEnabled =
enteredName.isNotEmpty || nameController.text.isNotEmpty;
if (widget.currentSpaceModel != null) { if (widget.currentSpaceModel != null) {
subspaces = []; subspaces = [];
tags = []; tags = [];
@@ -99,15 +96,13 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool isSpaceModelDisabled = (tags != null && tags!.isNotEmpty || bool isSpaceModelDisabled =
subspaces != null && subspaces!.isNotEmpty); (tags != null && tags!.isNotEmpty || subspaces != null && subspaces!.isNotEmpty);
bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null); bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null);
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
return AlertDialog( return AlertDialog(
title: widget.isEdit title: widget.isEdit ? const Text('Edit Space') : const Text('Create New Space'),
? const Text('Edit Space')
: const Text('Create New Space'),
backgroundColor: ColorsManager.whiteColors, backgroundColor: ColorsManager.whiteColors,
content: SizedBox( content: SizedBox(
width: screenWidth * 0.5, width: screenWidth * 0.5,
@@ -117,10 +112,50 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
children: [ children: [
Expanded( Expanded(
flex: 1, flex: 1,
child: IconChoosePartWidget( child: Column(
selectedIcon: selectedIcon, mainAxisAlignment: MainAxisAlignment.center,
showIconSelection: _showIconSelectionDialog, // crossAxisAlignment: CrossAxisAlignment.center,
screenWidth: screenWidth, children: [
const SizedBox(height: 50),
Stack(
alignment: Alignment.center,
children: [
Container(
width: screenWidth * 0.1,
height: screenWidth * 0.1,
decoration: const BoxDecoration(
color: ColorsManager.boxColor,
shape: BoxShape.circle,
),
),
SvgPicture.asset(
selectedIcon,
width: screenWidth * 0.04,
height: screenWidth * 0.04,
),
Positioned(
top: 20,
right: 20,
child: InkWell(
onTap: _showIconSelectionDialog,
child: Container(
width: 24,
height: 24,
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: SvgPicture.asset(
Assets.iconEdit,
width: 16,
height: 16,
),
),
),
),
],
),
],
), ),
), ),
const SizedBox(width: 20), const SizedBox(width: 20),
@@ -129,146 +164,342 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SpaceNameTextfieldWidget( SizedBox(
isNameFieldExist: isNameFieldExist, width: screenWidth * 0.25,
isNameFieldInvalid: isNameFieldInvalid, child: TextField(
nameController: nameController, controller: nameController,
screenWidth: screenWidth, onChanged: (value) {
onChange: (value) { enteredName = value.trim();
enteredName = value.trim(); setState(() {
setState(() { isNameFieldExist = false;
isNameFieldExist = false; isOkButtonEnabled = false;
isOkButtonEnabled = false; isNameFieldInvalid = value.isEmpty;
isNameFieldInvalid = value.isEmpty;
if (!isNameFieldInvalid) { if (!isNameFieldInvalid) {
if (SpaceHelper.isNameConflict( if (SpaceHelper.isNameConflict(
value, widget.parentSpace, widget.editSpace)) { value, widget.parentSpace, widget.editSpace)) {
isNameFieldExist = true; isNameFieldExist = true;
isOkButtonEnabled = false; isOkButtonEnabled = false;
} else { } else {
isNameFieldExist = false; isNameFieldExist = false;
isOkButtonEnabled = true; isOkButtonEnabled = true;
}
} }
} });
}); },
}, style: Theme.of(context).textTheme.bodyMedium,
), decoration: InputDecoration(
const SizedBox(height: 10), hintText: 'Please enter the name',
// SpaceModelLinkingWidget( hintStyle: Theme.of(context)
// isSpaceModelDisabled: true, .textTheme
// // isSpaceModelDisabled, .bodyMedium!
// onPressed: () { .copyWith(color: ColorsManager.lightGrayColor),
// isSpaceModelDisabled filled: true,
// ? null fillColor: ColorsManager.boxColor,
// : _showLinkSpaceModelDialog(context); enabledBorder: OutlineInputBorder(
// }, borderRadius: BorderRadius.circular(10),
// onDeleted: () => setState(() { borderSide: BorderSide(
// selectedSpaceModel = null; color: isNameFieldInvalid || isNameFieldExist
// subspaces = widget.subspaces ?? []; ? ColorsManager.red
// tags = widget.tags ?? []; : ColorsManager.boxColor,
// }), width: 1.5,
// screenWidth: screenWidth, ),
// selectedSpaceModel: selectedSpaceModel,
// ),
const SizedBox(height: 25),
// Row(
// children: [
// const Expanded(
// child: Divider(
// color: ColorsManager.neutralGray,
// thickness: 1.0,
// ),
// ),
// Padding(
// padding: const EdgeInsets.symmetric(horizontal: 6.0),
// child: Text(
// 'OR',
// style: Theme.of(context)
// .textTheme
// .bodyMedium
// ?.copyWith(fontWeight: FontWeight.bold),
// ),
// ),
// const Expanded(
// child: Divider(
// color: ColorsManager.neutralGray,
// thickness: 1.0,
// ),
// ),
// ],
// ),
const SizedBox(height: 25),
SubSpacePartWidget(
subspaces: subspaces,
onPressed: () {
isTagsAndSubspaceModelDisabled
? null
: _showSubSpaceDialog(
context,
enteredName,
[],
false,
widget.products,
subspaces,
);
},
isTagsAndSubspaceModelDisabled:
isTagsAndSubspaceModelDisabled,
screenWidth: screenWidth,
editChipOnTap: () async {
_showSubSpaceDialog(
context,
enteredName,
[],
true,
widget.products,
subspaces,
);
},
),
const SizedBox(height: 10),
DevicesPartWidget(
tags: tags,
subspaces: subspaces,
screenWidth: screenWidth,
isTagsAndSubspaceModelDisabled:
isTagsAndSubspaceModelDisabled,
onEditChip: () async {
await showDialog(
context: context,
builder: (context) => AssignTagDialog(
products: widget.products,
subspaces: subspaces,
allTags: widget.allTags,
addedProducts:
TagHelper.createInitialSelectedProductsForTags(
tags ?? [], subspaces),
title: 'Edit Device',
initialTags: TagHelper.generateInitialForTags(
spaceTags: tags, subspaces: subspaces),
spaceName: widget.name ?? '',
projectTags: widget.projectTags,
onSave: (updatedTags, updatedSubspaces) {
setState(() {
tags = updatedTags;
subspaces = updatedSubspaces;
});
},
), ),
); focusedBorder: OutlineInputBorder(
}, borderRadius: BorderRadius.circular(10),
onTextButtonPressed: () { borderSide: const BorderSide(
isTagsAndSubspaceModelDisabled color: ColorsManager.boxColor,
? null width: 1.5,
: _showTagCreateDialog( ),
context, ),
enteredName, ),
widget.isEdit, ),
widget.products, ),
); if (isNameFieldInvalid)
}, Padding(
) padding: const EdgeInsets.only(top: 8.0),
child: Text(
'*Space name should not be empty.',
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.red),
),
),
if (isNameFieldExist)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
'*Name already exist',
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.red),
),
),
const SizedBox(height: 10),
selectedSpaceModel == null
? TextButton(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
),
onPressed: () {
isSpaceModelDisabled ? null : _showLinkSpaceModelDialog(context);
},
child: ButtonContentWidget(
svgAssets: Assets.link,
label: 'Link a space model',
disabled: isSpaceModelDisabled,
),
)
: Container(
width: screenWidth * 0.25,
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
decoration: BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(10),
),
child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: [
Chip(
label: Text(
selectedSpaceModel?.modelName ?? '',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: ColorsManager.spaceColor),
),
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: const BorderSide(
color: ColorsManager.transparentColor,
width: 0,
),
),
deleteIcon: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.lightGrayColor,
width: 1.5,
),
),
child: const Icon(
Icons.close,
size: 16,
color: ColorsManager.lightGrayColor,
),
),
onDeleted: () => setState(() {
this.selectedSpaceModel = null;
subspaces = widget.subspaces ?? [];
tags = widget.tags ?? [];
})),
],
),
),
const SizedBox(height: 25),
Row(
children: [
const Expanded(
child: Divider(
color: ColorsManager.neutralGray,
thickness: 1.0,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 6.0),
child: Text(
'OR',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.bold),
),
),
const Expanded(
child: Divider(
color: ColorsManager.neutralGray,
thickness: 1.0,
),
),
],
),
const SizedBox(height: 25),
subspaces == null || subspaces!.isEmpty
? TextButton(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
overlayColor: ColorsManager.transparentColor,
),
onPressed: () async {
isTagsAndSubspaceModelDisabled
? null
: _showSubSpaceDialog(
context, enteredName, [], false, widget.products, subspaces);
},
child: ButtonContentWidget(
icon: Icons.add,
label: 'Create Sub Space',
disabled: isTagsAndSubspaceModelDisabled,
),
)
: SizedBox(
width: screenWidth * 0.25,
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: ColorsManager.textFieldGreyColor,
borderRadius: BorderRadius.circular(15),
border: Border.all(
color: ColorsManager.textFieldGreyColor,
width: 3.0, // Border width
),
),
child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: [
if (subspaces != null)
...subspaces!.map((subspace) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SubspaceNameDisplayWidget(
text: subspace.subspaceName,
validateName: (updatedName) {
bool nameExists = subspaces!.any((s) {
bool isSameId = s.internalId == subspace.internalId;
bool isSameName =
s.subspaceName.trim().toLowerCase() ==
updatedName.trim().toLowerCase();
return !isSameId && isSameName;
});
return !nameExists;
},
onNameChanged: (updatedName) {
setState(() {
subspace.subspaceName = updatedName;
});
},
),
],
);
}),
EditChip(
onTap: () async {
_showSubSpaceDialog(context, enteredName, [], true,
widget.products, subspaces);
},
)
],
),
),
),
const SizedBox(height: 10),
(tags?.isNotEmpty == true ||
subspaces?.any((subspace) => subspace.tags?.isNotEmpty == true) == true)
? SizedBox(
width: screenWidth * 0.25,
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: ColorsManager.textFieldGreyColor,
borderRadius: BorderRadius.circular(15),
border: Border.all(
color: ColorsManager.textFieldGreyColor,
width: 3.0, // Border width
),
),
child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: [
// Combine tags from spaceModel and subspaces
...TagHelper.groupTags([
...?tags,
...?subspaces?.expand((subspace) => subspace.tags ?? [])
]).entries.map(
(entry) => Chip(
avatar: SizedBox(
width: 24,
height: 24,
child: SvgPicture.asset(
entry.key.icon ?? 'assets/icons/gateway.svg',
fit: BoxFit.contain,
),
),
label: Text(
'x${entry.value}', // Show count
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.spaceColor),
),
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: const BorderSide(
color: ColorsManager.spaceColor,
),
),
),
),
EditChip(onTap: () async {
await showDialog(
context: context,
builder: (context) => AssignTagDialog(
products: widget.products,
subspaces: subspaces,
allTags: widget.allTags,
addedProducts:
TagHelper.createInitialSelectedProductsForTags(
tags ?? [], subspaces),
title: 'Edit Device',
initialTags: TagHelper.generateInitialForTags(
spaceTags: tags, subspaces: subspaces),
spaceName: widget.name ?? '',
projectTags: widget.projectTags,
onSave: (updatedTags, updatedSubspaces) {
setState(() {
tags = updatedTags;
subspaces = updatedSubspaces;
});
},
),
);
})
],
),
),
)
: TextButton(
onPressed: () {
isTagsAndSubspaceModelDisabled
? null
: _showTagCreateDialog(
context,
enteredName,
widget.isEdit,
widget.products,
);
},
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
),
child: ButtonContentWidget(
icon: Icons.add,
label: 'Add Devices',
disabled: isTagsAndSubspaceModelDisabled,
))
], ],
), ),
), ),
@@ -298,32 +529,17 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
} else if (isNameFieldExist) { } else if (isNameFieldExist) {
return; return;
} else { } else {
String newName = enteredName.isNotEmpty String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? '');
? enteredName
: (widget.name ?? '');
if (newName.isNotEmpty) { if (newName.isNotEmpty) {
if (tags != null && tags!.isNotEmpty) { widget.onCreateSpace(newName, selectedIcon, selectedProducts,
if (tags!.any( selectedSpaceModel, subspaces, tags);
(tag) => tag.uuid == null || tag.uuid!.isEmpty,
)) {
return;
}
}
widget.onCreateSpace(
newName,
selectedIcon,
selectedProducts,
selectedSpaceModel,
subspaces,
tags);
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
} }
}, },
borderRadius: 10, borderRadius: 10,
backgroundColor: isOkButtonEnabled backgroundColor:
? ColorsManager.secondaryColor isOkButtonEnabled ? ColorsManager.secondaryColor : ColorsManager.grayColor,
: ColorsManager.grayColor,
foregroundColor: ColorsManager.whiteColors, foregroundColor: ColorsManager.whiteColors,
child: const Text('OK'), child: const Text('OK'),
), ),
@@ -334,7 +550,6 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
); );
} }
//dialooogggs
void _showIconSelectionDialog() { void _showIconSelectionDialog() {
showDialog( showDialog(
context: context, context: context,
@@ -371,50 +586,26 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
); );
} }
void _showSubSpaceDialog( void _showSubSpaceDialog(BuildContext context, String name, final List<Tag>? spaceTags,
BuildContext context, bool isEdit, List<ProductModel>? products, final List<SubspaceModel>? existingSubSpaces) {
String name,
final List<Tag>? spaceTags,
bool isEdit,
List<ProductModel>? products,
final List<SubspaceModel>? existingSubSpaces,
) {
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return CreateSubSpaceDialog( return CreateSubSpaceDialog(
spaceName: name, spaceName: name,
dialogTitle: isEdit ? 'Edit Sub-spaces' : 'Create Sub-spaces', dialogTitle: isEdit ? 'Edit Sub-space' : 'Create Sub-space',
products: products, products: products,
existingSubSpaces: existingSubSpaces, existingSubSpaces: existingSubSpaces,
onSave: (slectedSubspaces, updatedSubSpaces) { onSave: (slectedSubspaces) {
final List<Tag> tagsToAppendToSpace = []; final List<Tag> tagsToAppendToSpace = [];
if (slectedSubspaces != null && slectedSubspaces.isNotEmpty) { if (slectedSubspaces != null) {
final updatedIds = final updatedIds = slectedSubspaces.map((s) => s.internalId).toSet();
slectedSubspaces.map((s) => s.internalId).toSet();
if (existingSubSpaces != null) { if (existingSubSpaces != null) {
final deletedSubspaces = existingSubSpaces final deletedSubspaces =
.where((s) => !updatedIds.contains(s.internalId)) existingSubSpaces.where((s) => !updatedIds.contains(s.internalId)).toList();
.toList();
for (var s in deletedSubspaces) { for (var s in deletedSubspaces) {
if (s.tags != null) { if (s.tags != null) {
s.tags!.forEach(
(tag) => tag.location = null,
);
tagsToAppendToSpace.addAll(s.tags!);
}
}
}
} else {
if (existingSubSpaces != null) {
final deletedSubspaces = existingSubSpaces;
for (var s in deletedSubspaces) {
if (s.tags != null) {
s.tags!.forEach(
(tag) => tag.location = null,
);
tagsToAppendToSpace.addAll(s.tags!); tagsToAppendToSpace.addAll(s.tags!);
} }
} }
@@ -432,16 +623,15 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
); );
} }
void _showTagCreateDialog(BuildContext context, String name, bool isEdit, void _showTagCreateDialog(
List<ProductModel>? products) { BuildContext context, String name, bool isEdit, List<ProductModel>? products) {
isEdit isEdit
? showDialog( ? showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return AssignTagDialog( return AssignTagDialog(
title: 'Edit Device', title: 'Edit Device',
addedProducts: TagHelper.createInitialSelectedProductsForTags( addedProducts: TagHelper.createInitialSelectedProductsForTags(tags, subspaces),
tags, subspaces),
spaceName: name, spaceName: name,
products: products, products: products,
subspaces: subspaces, subspaces: subspaces,
@@ -456,8 +646,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
if (subspaces != null) { if (subspaces != null) {
for (final subspace in subspaces!) { for (final subspace in subspaces!) {
for (final selectedSubspace in selectedSubspaces) { for (final selectedSubspace in selectedSubspaces) {
if (subspace.subspaceName == if (subspace.subspaceName == selectedSubspace.subspaceName) {
selectedSubspace.subspaceName) {
subspace.tags = selectedSubspace.tags; subspace.tags = selectedSubspace.tags;
} }
} }
@@ -481,8 +670,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
allTags: widget.allTags, allTags: widget.allTags,
projectTags: widget.projectTags, projectTags: widget.projectTags,
initialSelectedProducts: initialSelectedProducts:
TagHelper.createInitialSelectedProductsForTags( TagHelper.createInitialSelectedProductsForTags(tags, subspaces),
tags, subspaces),
onSave: (selectedSpaceTags, selectedSubspaces) { onSave: (selectedSpaceTags, selectedSubspaces) {
setState(() { setState(() {
tags = selectedSpaceTags; tags = selectedSpaceTags;
@@ -492,8 +680,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
if (subspaces != null) { if (subspaces != null) {
for (final subspace in subspaces!) { for (final subspace in subspaces!) {
for (final selectedSubspace in selectedSubspaces) { for (final selectedSubspace in selectedSubspaces) {
if (subspace.subspaceName == if (subspace.subspaceName == selectedSubspace.subspaceName) {
selectedSubspace.subspaceName) {
subspace.tags = selectedSubspace.tags; subspace.tags = selectedSubspace.tags;
} }
} }
@@ -5,7 +5,7 @@ class PlusButtonWidget extends StatelessWidget {
final int index; final int index;
final String direction; final String direction;
final Offset offset; final Offset offset;
final Function(int index, Offset newPosition) onButtonTap; final Function(int index, Offset newPosition, String direction) onButtonTap;
const PlusButtonWidget({ const PlusButtonWidget({
super.key, super.key,
@@ -17,21 +17,35 @@ class PlusButtonWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return Positioned(
onTap: () { left: offset.dx,
onButtonTap(index, const Offset(0, 150)); top: offset.dy,
}, child: GestureDetector(
child: Container( onTap: () {
width: 30, Offset newPosition;
height: 30, switch (direction) {
decoration: const BoxDecoration( case 'left':
color: ColorsManager.spaceColor, newPosition = const Offset(-200, 0);
shape: BoxShape.circle, break;
), case 'right':
child: const Icon( newPosition = const Offset(200, 0);
Icons.add, break;
color: ColorsManager.whiteColors, case 'down':
size: 20, newPosition = const Offset(0, 150);
break;
default:
newPosition = Offset.zero;
}
onButtonTap(index, newPosition, direction);
},
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),
), ),
), ),
); );
@@ -7,7 +7,7 @@ class SpaceCardWidget extends StatelessWidget {
final Offset position; final Offset position;
final bool isHovered; final bool isHovered;
final Function(int index, bool isHovered) onHoverChanged; final Function(int index, bool isHovered) onHoverChanged;
final Function(int index, Offset newPosition) onButtonTap; final Function(int index, Offset newPosition, String direction) onButtonTap;
final Widget Function(int index) buildSpaceContainer; final Widget Function(int index) buildSpaceContainer;
final ValueChanged<Offset> onPositionChanged; final ValueChanged<Offset> onPositionChanged;
@@ -25,34 +25,35 @@ class SpaceCardWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MouseRegion( return GestureDetector(
onEnter: (_) => onHoverChanged(index, true), behavior: HitTestBehavior.opaque,
onExit: (_) => onHoverChanged(index, false), onPanUpdate: (details) {
child: SizedBox( // Call the provided callback to update the position
width: 140, // Make sure this covers both card and plus button final newPosition = position + details.delta;
height: 90, onPositionChanged(newPosition);
},
child: MouseRegion(
onEnter: (_) {
// Call the provided callback to handle hover state
onHoverChanged(index, true);
},
onExit: (_) {
// Call the provided callback to handle hover state
onHoverChanged(index, false);
},
child: Stack( child: Stack(
clipBehavior: Clip.none, clipBehavior: Clip
.none, // Allow hovering elements to be displayed outside the boundary
children: [ children: [
// Main card buildSpaceContainer(index), // Build the space container
Container( if (isHovered) ...[
width: 140, PlusButtonWidget(
height: 80, index: index,
alignment: Alignment.center, direction: 'down',
color: Colors.transparent, offset: const Offset(63, 50),
child: buildSpaceContainer(index), onButtonTap: onButtonTap,
),
// Plus button (NO inner Positioned!)
if (isHovered)
Align(
alignment: Alignment.bottomCenter,
child: PlusButtonWidget(
index: index,
direction: 'down',
offset: Offset.zero,
onButtonTap: onButtonTap,
),
), ),
],
], ],
), ),
), ),
@@ -13,8 +13,7 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final existingTagCounts = <String, int>{}; final existingTagCounts = <String, int>{};
for (var tag in initialTags) { for (var tag in initialTags) {
if (tag.product != null) { if (tag.product != null) {
existingTagCounts[tag.product!.uuid] = existingTagCounts[tag.product!.uuid] = (existingTagCounts[tag.product!.uuid] ?? 0) + 1;
(existingTagCounts[tag.product!.uuid] ?? 0) + 1;
} }
} }
@@ -23,17 +22,14 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
for (var selectedProduct in event.addedProducts) { for (var selectedProduct in event.addedProducts) {
final existingCount = existingTagCounts[selectedProduct.productId] ?? 0; final existingCount = existingTagCounts[selectedProduct.productId] ?? 0;
if (selectedProduct.count == 0 || if (selectedProduct.count == 0 || selectedProduct.count <= existingCount) {
selectedProduct.count <= existingCount) { tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
continue; continue;
} }
final missingCount = selectedProduct.count - existingCount; final missingCount = selectedProduct.count - existingCount;
tags.addAll(initialTags tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
.where((tag) => tag.product?.uuid == selectedProduct.productId));
if (missingCount > 0) { if (missingCount > 0) {
tags.addAll(List.generate( tags.addAll(List.generate(
@@ -89,8 +85,7 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final tags = List<Tag>.from(currentState.tags); final tags = List<Tag>.from(currentState.tags);
// Update the location // Update the location
tags[event.index] = tags[event.index] = tags[event.index].copyWith(location: event.location);
tags[event.index].copyWith(location: event.location);
final updatedTags = _calculateAvailableTags(projectTags, tags); final updatedTags = _calculateAvailableTags(projectTags, tags);
@@ -122,8 +117,7 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final currentState = state; final currentState = state;
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags) final tags = List<Tag>.from(currentState.tags)..remove(event.tagToDelete);
..remove(event.tagToDelete);
// Recalculate available tags // Recalculate available tags
final updatedTags = _calculateAvailableTags(projectTags, tags); final updatedTags = _calculateAvailableTags(projectTags, tags);
@@ -147,10 +141,8 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
// Get validation error for duplicate tags // Get validation error for duplicate tags
String? _getValidationError(List<Tag> tags) { String? _getValidationError(List<Tag> tags) {
final nonEmptyTags = tags final nonEmptyTags =
.map((tag) => tag.tag?.trim() ?? '') tags.map((tag) => tag.tag?.trim() ?? '').where((tag) => tag.isNotEmpty).toList();
.where((tag) => tag.isNotEmpty)
.toList();
final duplicateTags = nonEmptyTags final duplicateTags = nonEmptyTags
.fold<Map<String, int>>({}, (map, tag) { .fold<Map<String, int>>({}, (map, tag) {
@@ -176,11 +168,9 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
.toSet(); .toSet();
final availableTags = allTags final availableTags = allTags
.where((tag) => .where((tag) => tag.tag != null && !selectedTagSet.contains(tag.tag!.trim()))
tag.tag != null && !selectedTagSet.contains(tag.tag!.trim()))
.toList(); .toList();
return projectTags; return availableTags;
// availableTags;
} }
} }
@@ -1,5 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/dialog_dropdown.dart';
import 'package:syncrow_web/common/tag_dialog_textfield_dropdown.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
@@ -7,10 +12,9 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/widgets/assign_tags_tables_widget.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:uuid/uuid.dart';
import 'widgets/save_add_device_row_widget.dart';
class AssignTagDialog extends StatelessWidget { class AssignTagDialog extends StatelessWidget {
final List<ProductModel>? products; final List<ProductModel>? products;
@@ -25,7 +29,7 @@ class AssignTagDialog extends StatelessWidget {
final List<Tag> projectTags; final List<Tag> projectTags;
const AssignTagDialog( const AssignTagDialog(
{super.key, {Key? key,
required this.products, required this.products,
required this.subspaces, required this.subspaces,
required this.addedProducts, required this.addedProducts,
@@ -35,14 +39,13 @@ class AssignTagDialog extends StatelessWidget {
required this.spaceName, required this.spaceName,
required this.title, required this.title,
this.onSave, this.onSave,
required this.projectTags}); required this.projectTags})
: super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<String> locations = (subspaces ?? []) final List<String> locations =
.map((subspace) => subspace.subspaceName) (subspaces ?? []).map((subspace) => subspace.subspaceName).toList()..add('Main Space');
.toList()
..add('Main Space');
return BlocProvider( return BlocProvider(
create: (_) => AssignTagBloc(projectTags) create: (_) => AssignTagBloc(projectTags)
@@ -64,31 +67,131 @@ class AssignTagDialog extends StatelessWidget {
content: SingleChildScrollView( content: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
AssignTagsTable( ClipRRect(
controllers: controllers, borderRadius: BorderRadius.circular(20),
locations: locations, child: DataTable(
tags: state.tags, headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey),
updatedTags: state.updatedTags, key: ValueKey(state.tags.length),
onDeleteDevice: ({required index, required tag}) { border: TableBorder.all(
context color: ColorsManager.dataHeaderGrey,
.read<AssignTagBloc>() width: 1,
.add(DeleteTag(tagToDelete: tag, tags: state.tags)); borderRadius: BorderRadius.circular(20),
),
columns: [
DataColumn(
label: Text('#', style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label: Text('Device', style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
numeric: false,
label: Text('Tag', style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label:
Text('Location', style: Theme.of(context).textTheme.bodyMedium)),
],
rows: state.tags.isEmpty
? [
DataRow(cells: [
DataCell(
Center(
child: Text('No Data Available',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: ColorsManager.lightGrayColor,
)),
),
),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
])
]
: List.generate(state.tags.length, (index) {
final tag = state.tags[index];
final controller = controllers[index];
controllers.removeAt(index); return DataRow(
}, cells: [
onTagDropDownSelected: ({required index, required tag}) { DataCell(Text((index + 1).toString())),
context.read<AssignTagBloc>().add(UpdateTagEvent( DataCell(
index: index, Row(
tag: tag, mainAxisAlignment: MainAxisAlignment.spaceBetween,
)); children: [
}, Expanded(
onLocationDropDownSelected: ( child: Text(
{required index, required location}) { tag.product?.name ?? 'Unknown',
context.read<AssignTagBloc>().add(UpdateLocation( overflow: TextOverflow.ellipsis,
index: index, )),
location: location, const SizedBox(width: 10),
)); Container(
}, width: 20.0,
height: 20.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.lightGrayColor,
width: 1.0,
),
),
child: IconButton(
icon: const Icon(
Icons.close,
color: ColorsManager.lightGreyColor,
size: 16,
),
onPressed: () {
context.read<AssignTagBloc>().add(
DeleteTag(tagToDelete: tag, tags: state.tags));
controllers.removeAt(index);
},
tooltip: 'Delete Tag',
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
),
],
),
),
DataCell(
Container(
alignment:
Alignment.centerLeft, // Align cell content to the left
child: SizedBox(
width: double.infinity,
child: TagDialogTextfieldDropdown(
key: ValueKey('dropdown_${const Uuid().v4()}_$index'),
items: state.updatedTags,
product: tag.product?.uuid ?? 'Unknown',
initialValue: tag,
onSelected: (value) {
controller.text = value.tag ?? '';
context.read<AssignTagBloc>().add(UpdateTagEvent(
index: index,
tag: value,
));
},
),
),
),
),
DataCell(
SizedBox(
width: double.infinity,
child: DialogDropdown(
items: locations,
selectedValue: tag.location ?? 'Main Space',
onSelected: (value) {
context.read<AssignTagBloc>().add(UpdateLocation(
index: index,
location: value,
));
},
)),
),
],
);
}),
),
), ),
if (state.errorMessage != null) if (state.errorMessage != null)
Text(state.errorMessage!, Text(state.errorMessage!,
@@ -100,15 +203,69 @@ class AssignTagDialog extends StatelessWidget {
), ),
), ),
actions: [ actions: [
SaveAddDeviceRowWidget( Row(
subspaces: subspaces, mainAxisAlignment: MainAxisAlignment.spaceAround,
products: products, children: [
projectTags: projectTags, const SizedBox(width: 10),
spaceName: spaceName, Expanded(
onSave: onSave, child: Builder(
allTags: allTags, builder: (buttonContext) => CancelButton(
tags: state.tags, label: 'Add New Device',
isSaveEnabled: state.isSaveEnabled, onPressed: () async {
final updatedTags = List<Tag>.from(state.tags);
final result = TagHelper.processTags(updatedTags, subspaces);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces =
List<SubspaceModel>.from(result['subspaces'] as List<dynamic>);
Navigator.of(context).pop();
await showDialog(
context: context,
builder: (context) => AddDeviceTypeWidget(
products: products,
subspaces: processedSubspaces,
projectTags: projectTags,
initialSelectedProducts:
TagHelper.createInitialSelectedProductsForTags(
processedTags, processedSubspaces),
spaceName: spaceName,
spaceTags: processedTags,
isCreate: false,
onSave: onSave,
allTags: allTags,
),
);
},
),
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
borderRadius: 10,
backgroundColor: ColorsManager.secondaryColor,
foregroundColor: state.isSaveEnabled
? ColorsManager.whiteColors
: ColorsManager.whiteColorsWithOpacity,
onPressed: state.isSaveEnabled
? () async {
final updatedTags = List<Tag>.from(state.tags);
final result = TagHelper.processTags(updatedTags, subspaces);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces =
List<SubspaceModel>.from(result['subspaces'] as List<dynamic>);
onSave?.call(processedTags, processedSubspaces);
Navigator.of(context).pop();
}
: null,
child: const Text('Save'),
),
),
const SizedBox(width: 10),
],
), ),
], ],
); );
@@ -1,100 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import '../../../add_device_type/views/add_device_type_widget.dart';
import '../../../helper/tag_helper.dart';
class SaveAddDeviceRowWidget extends StatelessWidget {
const SaveAddDeviceRowWidget({
super.key,
required this.subspaces,
required this.products,
required this.projectTags,
required this.spaceName,
required this.onSave,
required this.allTags,
required this.tags,
required this.isSaveEnabled,
});
final List<Tag> tags;
final List<SubspaceModel>? subspaces;
final List<ProductModel>? products;
final List<Tag> projectTags;
final String spaceName;
final Function(List<Tag> p1, List<SubspaceModel>? p2)? onSave;
final List<String>? allTags;
final bool isSaveEnabled;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const SizedBox(width: 10),
Expanded(
child: Builder(
builder: (buttonContext) => CancelButton(
label: 'Add New Device',
onPressed: () async {
final updatedTags = List<Tag>.from(tags);
final result = TagHelper.processTags(updatedTags, subspaces);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces = List<SubspaceModel>.from(
result['subspaces'] as List<dynamic>);
Navigator.of(context).pop();
await showDialog(
context: context,
builder: (context) => AddDeviceTypeWidget(
products: products,
subspaces: processedSubspaces,
projectTags: projectTags,
initialSelectedProducts:
TagHelper.createInitialSelectedProductsForTags(
processedTags, processedSubspaces),
spaceName: spaceName,
spaceTags: processedTags,
isCreate: false,
onSave: onSave,
allTags: allTags,
),
);
},
),
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
borderRadius: 10,
backgroundColor: ColorsManager.secondaryColor,
foregroundColor: isSaveEnabled
? ColorsManager.whiteColors
: ColorsManager.whiteColorsWithOpacity,
onPressed: isSaveEnabled
? () async {
final updatedTags = List<Tag>.from(tags);
final result =
TagHelper.processTags(updatedTags, subspaces);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces = List<SubspaceModel>.from(
result['subspaces'] as List<dynamic>);
onSave?.call(processedTags, processedSubspaces);
Navigator.of(context).pop();
}
: null,
child: const Text('Save'),
),
),
const SizedBox(width: 10),
],
);
}
}
@@ -1,5 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/dialog_dropdown.dart';
import 'package:syncrow_web/common/tag_dialog_textfield_dropdown.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
@@ -8,10 +12,11 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assig
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
import 'widgets/RowOfCancelSaveWidget.dart'; import 'package:uuid/uuid.dart';
import 'widgets/assign_tags_tables_widget.dart';
class AssignTagModelsDialog extends StatelessWidget { class AssignTagModelsDialog extends StatelessWidget {
final List<ProductModel>? products; final List<ProductModel>? products;
@@ -48,10 +53,8 @@ class AssignTagModelsDialog extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<String> locations = (subspaces ?? []) final List<String> locations =
.map((subspace) => subspace.subspaceName) (subspaces ?? []).map((subspace) => subspace.subspaceName).toList()..add('Main Space');
.toList()
..add('Main Space');
return BlocProvider( return BlocProvider(
create: (_) => AssignTagModelBloc(projectTags) create: (_) => AssignTagModelBloc(projectTags)
@@ -75,38 +78,137 @@ class AssignTagModelsDialog extends StatelessWidget {
content: SingleChildScrollView( content: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
AssignTagsTable( ClipRRect(
controllers: controllers, borderRadius: BorderRadius.circular(20),
locations: locations, child: DataTable(
tags: state.tags, headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey),
updatedTags: state.updatedTags, key: ValueKey(state.tags.length),
onDeleteDevice: ({required index, required tag}) { border: TableBorder.all(
context color: ColorsManager.dataHeaderGrey,
.read<AssignTagModelBloc>() width: 1,
.add(DeleteTagModel( borderRadius: BorderRadius.circular(20),
tagToDelete: tag, ),
tags: state.tags, columns: [
)); DataColumn(
controllers.removeAt(index); label: Text('#', style: Theme.of(context).textTheme.bodyMedium)),
}, DataColumn(
onTagDropDownSelected: ( label: Text('Device',
{required index, required tag}) { style: Theme.of(context).textTheme.bodyMedium)),
context.read<AssignTagModelBloc>().add( DataColumn(
UpdateTag( numeric: false,
index: index, label:
tag: tag, Text('Tag', style: Theme.of(context).textTheme.bodyMedium)),
), DataColumn(
); label: Text('Location',
}, style: Theme.of(context).textTheme.bodyMedium)),
onLocationDropDownSelected: ( ],
{required index, required location}) { rows: state.tags.isEmpty
context.read<AssignTagModelBloc>().add( ? [
UpdateLocation( DataRow(cells: [
index: index, DataCell(
location: location, Center(
), child: Text('No Devices Available',
); style:
}, Theme.of(context).textTheme.bodyMedium?.copyWith(
color: ColorsManager.lightGrayColor,
)),
),
),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
])
]
: List.generate(state.tags.length, (index) {
final tag = state.tags[index];
final controller = controllers[index];
return DataRow(
cells: [
DataCell(Text((index + 1).toString())),
DataCell(
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
tag.product?.name ?? 'Unknown',
overflow: TextOverflow.ellipsis,
)),
const SizedBox(width: 10),
Container(
width: 20.0,
height: 20.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.lightGrayColor,
width: 1.0,
),
),
child: IconButton(
icon: const Icon(
Icons.close,
color: ColorsManager.lightGreyColor,
size: 16,
),
onPressed: () {
context.read<AssignTagModelBloc>().add(
DeleteTagModel(
tagToDelete: tag, tags: state.tags));
controllers.removeAt(index);
},
tooltip: 'Delete Tag',
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
),
],
),
),
DataCell(
Container(
alignment: Alignment
.centerLeft, // Align cell content to the left
child: SizedBox(
width: double.infinity,
child: TagDialogTextfieldDropdown(
key: ValueKey(
'dropdown_${const Uuid().v4()}_$index'),
product: tag.product?.uuid ?? 'Unknown',
items: state.updatedTags,
initialValue: tag,
onSelected: (value) {
controller.text = value.tag ?? '';
context.read<AssignTagModelBloc>().add(UpdateTag(
index: index,
tag: value,
));
},
),
),
),
),
DataCell(
SizedBox(
width: double.infinity,
child: DialogDropdown(
items: locations,
selectedValue: tag.location ?? 'Main Space',
onSelected: (value) {
context
.read<AssignTagModelBloc>()
.add(UpdateLocation(
index: index,
location: value,
));
},
)),
),
],
);
}),
),
), ),
if (state.errorMessage != null) if (state.errorMessage != null)
Text(state.errorMessage!, Text(state.errorMessage!,
@@ -118,16 +220,101 @@ class AssignTagModelsDialog extends StatelessWidget {
), ),
), ),
actions: [ actions: [
RowOfSaveCancelWidget( Row(
subspaces: subspaces, mainAxisAlignment: MainAxisAlignment.spaceAround,
products: products, children: [
allTags: allTags, const SizedBox(width: 10),
spaceName: spaceName, Expanded(
otherSpaceModels: otherSpaceModels, child: Builder(
pageContext: pageContext, builder: (buttonContext) => CancelButton(
projectTags: projectTags, label: 'Add New Device',
spaceModel: spaceModel, onPressed: () async {
allSpaceModels: allSpaceModels, final updatedTags = List<Tag>.from(state.tags);
final result =
TagHelper.updateSubspaceTagModels(updatedTags, subspaces);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces = List<SubspaceTemplateModel>.from(
result['subspaces'] as List<dynamic>);
if (context.mounted) {
Navigator.of(context).pop();
await showDialog<bool>(
barrierDismissible: false,
context: context,
builder: (dialogContext) => AddDeviceTypeModelWidget(
products: products,
subspaces: processedSubspaces,
isCreate: false,
initialSelectedProducts:
TagHelper.createInitialSelectedProducts(
processedTags, processedSubspaces),
allTags: allTags,
spaceName: spaceName,
otherSpaceModels: otherSpaceModels,
spaceTagModels: processedTags,
pageContext: pageContext,
projectTags: projectTags,
spaceModel: SpaceTemplateModel(
modelName: spaceName,
tags: updatedTags,
uuid: spaceModel?.uuid,
internalId: spaceModel?.internalId,
subspaceModels: processedSubspaces)),
);
}
},
),
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
borderRadius: 10,
backgroundColor: ColorsManager.secondaryColor,
foregroundColor: state.isSaveEnabled
? ColorsManager.whiteColors
: ColorsManager.whiteColorsWithOpacity,
onPressed: state.isSaveEnabled
? () async {
final updatedTags = List<Tag>.from(state.tags);
final result =
TagHelper.updateSubspaceTagModels(updatedTags, subspaces);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces = List<SubspaceTemplateModel>.from(
result['subspaces'] as List<dynamic>);
Navigator.of(context).popUntil((route) => route.isFirst);
await showDialog(
context: context,
builder: (BuildContext dialogContext) {
return CreateSpaceModelDialog(
products: products,
allSpaceModels: allSpaceModels,
allTags: allTags,
projectTags: projectTags,
pageContext: pageContext,
otherSpaceModels: otherSpaceModels,
spaceModel: SpaceTemplateModel(
modelName: spaceName,
tags: processedTags,
uuid: spaceModel?.uuid,
internalId: spaceModel?.internalId,
subspaceModels: processedSubspaces),
);
},
);
}
: null,
child: const Text('Save'),
),
),
const SizedBox(width: 10),
],
), ),
], ],
); );
@@ -1,152 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../utils/color_manager.dart';
import '../../../../common/buttons/cancel_button.dart';
import '../../../../common/buttons/default_button.dart';
import '../../../all_spaces/model/product_model.dart';
import '../../../all_spaces/model/tag.dart';
import '../../../helper/tag_helper.dart';
import '../../../space_model/models/space_template_model.dart';
import '../../../space_model/models/subspace_template_model.dart';
import '../../../space_model/widgets/dialog/create_space_model_dialog.dart';
import '../../../tag_model/views/add_device_type_model_widget.dart';
import '../../bloc/assign_tag_model_bloc.dart';
import '../../bloc/assign_tag_model_state.dart';
class RowOfSaveCancelWidget extends StatelessWidget {
const RowOfSaveCancelWidget({
super.key,
required this.subspaces,
required this.products,
required this.allTags,
required this.spaceName,
required this.otherSpaceModels,
required this.pageContext,
required this.projectTags,
required this.spaceModel,
required this.allSpaceModels,
});
final List<SubspaceTemplateModel>? subspaces;
final List<ProductModel>? products;
final List<String>? allTags;
final String spaceName;
final List<String>? otherSpaceModels;
final BuildContext? pageContext;
final List<Tag> projectTags;
final SpaceTemplateModel? spaceModel;
final List<SpaceTemplateModel>? allSpaceModels;
@override
Widget build(BuildContext context) {
return BlocBuilder<AssignTagModelBloc, AssignTagModelState>(
builder: (context, state) {
if (state is AssignTagModelLoaded) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const SizedBox(width: 10),
Expanded(
child: Builder(
builder: (buttonContext) => CancelButton(
label: 'Add New Device',
onPressed: () async {
final updatedTags = List<Tag>.from(state.tags);
final result = TagHelper.updateSubspaceTagModels(
updatedTags, subspaces);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces =
List<SubspaceTemplateModel>.from(
result['subspaces'] as List<dynamic>);
if (context.mounted) {
Navigator.of(context).pop();
await showDialog<bool>(
barrierDismissible: false,
context: context,
builder: (dialogContext) => AddDeviceTypeModelWidget(
products: products,
subspaces: processedSubspaces,
isCreate: false,
initialSelectedProducts:
TagHelper.createInitialSelectedProducts(
processedTags, processedSubspaces),
allTags: allTags,
spaceName: spaceName,
otherSpaceModels: otherSpaceModels,
spaceTagModels: processedTags,
pageContext: pageContext,
projectTags: projectTags,
spaceModel: SpaceTemplateModel(
modelName: spaceName,
tags: updatedTags,
uuid: spaceModel?.uuid,
internalId: spaceModel?.internalId,
subspaceModels: processedSubspaces)),
);
}
},
),
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
borderRadius: 10,
backgroundColor: ColorsManager.secondaryColor,
foregroundColor: state.isSaveEnabled
? ColorsManager.whiteColors
: ColorsManager.whiteColorsWithOpacity,
onPressed: state.isSaveEnabled
? () async {
final updatedTags = List<Tag>.from(state.tags);
final result = TagHelper.updateSubspaceTagModels(
updatedTags, subspaces);
final processedTags =
result['updatedTags'] as List<Tag>;
final processedSubspaces =
List<SubspaceTemplateModel>.from(
result['subspaces'] as List<dynamic>);
Navigator.of(context)
.popUntil((route) => route.isFirst);
await showDialog(
context: context,
builder: (BuildContext dialogContext) {
return CreateSpaceModelDialog(
products: products,
allSpaceModels: allSpaceModels,
allTags: allTags,
projectTags: projectTags,
pageContext: pageContext,
otherSpaceModels: otherSpaceModels,
spaceModel: SpaceTemplateModel(
modelName: spaceName,
tags: processedTags,
uuid: spaceModel?.uuid,
internalId: spaceModel?.internalId,
subspaceModels: processedSubspaces),
);
},
);
}
: null,
child: const Text('Save'),
),
),
const SizedBox(width: 10),
],
);
} else {
return const SizedBox();
}
},
);
}
}
@@ -1,155 +0,0 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:uuid/uuid.dart';
import '../../../../../common/dialog_dropdown.dart';
import '../../../../../common/tag_dialog_textfield_dropdown.dart';
import '../../../../../utils/color_manager.dart';
class AssignTagsTable extends StatelessWidget {
const AssignTagsTable({
super.key,
required this.controllers,
required this.locations,
required this.tags,
required this.updatedTags,
required this.onDeleteDevice,
required this.onLocationDropDownSelected,
required this.onTagDropDownSelected,
});
final void Function({required Tag tag, required int index})
onTagDropDownSelected;
final void Function({required String location, required int index})
onLocationDropDownSelected;
final void Function({required Tag tag, required int index}) onDeleteDevice;
final List<Tag> tags;
final List<Tag> updatedTags;
final List<TextEditingController> controllers;
final List<String> locations;
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(20),
child: DataTable(
headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey),
key: ValueKey(tags.length),
border: TableBorder.all(
color: ColorsManager.dataHeaderGrey,
width: 1,
borderRadius: BorderRadius.circular(20),
),
columns: [
DataColumn(
label: Text('#', style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label: Text('Device',
style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
numeric: false,
label:
Text('Tag', style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label: Text('Location',
style: Theme.of(context).textTheme.bodyMedium)),
],
rows: tags.isEmpty
? [
DataRow(cells: [
DataCell(
Center(
child: Text('No Devices Available',
style:
Theme.of(context).textTheme.bodyMedium?.copyWith(
color: ColorsManager.lightGrayColor,
)),
),
),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
const DataCell(SizedBox()),
])
]
: List.generate(tags.length, (index) {
final tag = tags[index];
final controller = controllers[index];
return DataRow(
cells: [
DataCell(Text((index + 1).toString())),
DataCell(
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
tag.product?.name ?? 'Unknown',
overflow: TextOverflow.ellipsis,
)),
const SizedBox(width: 10),
Container(
width: 20.0,
height: 20.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager.lightGrayColor,
width: 1.0,
),
),
child: IconButton(
icon: const Icon(
Icons.close,
color: ColorsManager.lightGreyColor,
size: 16,
),
onPressed: () {
onDeleteDevice(tag: tag, index: index);
},
tooltip: 'Delete Tag',
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
),
],
),
),
DataCell(
Container(
alignment: Alignment
.centerLeft, // Align cell content to the left
child: SizedBox(
width: double.infinity,
child: TagDialogTextfieldDropdown(
key: ValueKey(
'dropdown_${const Uuid().v4()}_$index'),
product: tag.product?.uuid ?? 'Unknown',
items: updatedTags,
initialValue: tag,
onSelected: (value) {
controller.text = value.tag ?? '';
onTagDropDownSelected(tag: value, index: index);
},
),
),
),
),
DataCell(
SizedBox(
width: double.infinity,
child: DialogDropdown(
items: locations,
selectedValue: tag.location ?? 'Main Space',
onSelected: (value) {
onLocationDropDownSelected(
location: value, index: index);
},
)),
),
],
);
}),
),
);
}
}
@@ -77,7 +77,7 @@ class SubSpaceBloc extends Bloc<SubSpaceEvent, SubSpaceState> {
emit(SubSpaceState( emit(SubSpaceState(
updatedSubSpaces, updatedSubSpaces,
updatedSubspaceModels, updatedSubspaceModels,
errorMessage , errorMessage,
updatedDuplicates, updatedDuplicates,
)); ));
}); });
@@ -1,23 +1,23 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_event.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_event.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_state.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_state.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'widgets/ok_cancel_sub_space_widget.dart';
import 'widgets/textfield_sub_space_dialog_widget.dart';
class CreateSubSpaceDialog extends StatefulWidget { class CreateSubSpaceDialog extends StatefulWidget {
final String dialogTitle; final String dialogTitle;
final List<SubspaceModel>? existingSubSpaces; final List<SubspaceModel>? existingSubSpaces;
final String? spaceName; final String? spaceName;
final List<ProductModel>? products; final List<ProductModel>? products;
final void Function( final void Function(List<SubspaceModel>?)? onSave;
List<SubspaceModel>?, List<UpdateSubspaceModel> updatedSubSpaces)? onSave;
const CreateSubSpaceDialog({ const CreateSubSpaceDialog({
required this.dialogTitle, required this.dialogTitle,
@@ -33,7 +33,14 @@ class CreateSubSpaceDialog extends StatefulWidget {
} }
class _CreateSubSpaceDialogState extends State<CreateSubSpaceDialog> { class _CreateSubSpaceDialogState extends State<CreateSubSpaceDialog> {
final TextEditingController _subspaceNameController = TextEditingController(); late final TextEditingController _subspaceNameController;
@override
void initState() {
_subspaceNameController = TextEditingController();
super.initState();
}
@override @override
void dispose() { void dispose() {
_subspaceNameController.dispose(); _subspaceNameController.dispose();
@@ -72,26 +79,83 @@ class _CreateSubSpaceDialogState extends State<CreateSubSpaceDialog> {
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
), ),
), ),
Row(
children: [
Text(
'press Enter to Save',
style: context.textTheme.headlineLarge?.copyWith(
fontWeight: FontWeight.w500,
fontSize: 10,
color: ColorsManager.grayColor,
),
),
const SizedBox(
width: 5,
),
const Icon(Icons.save_as_sharp, size: 10),
],
),
const SizedBox(height: 16), const SizedBox(height: 16),
TextFieldSubSpaceDialogWidget( Container(
subspaceNameController: _subspaceNameController, width: context.screenWidth * 0.35,
subSpaces: state.subSpaces, padding: const EdgeInsets.symmetric(
vertical: 10,
horizontal: 16,
),
decoration: BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(10),
),
child: Wrap(
spacing: 8,
runSpacing: 8,
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
...state.subSpaces.asMap().entries.map(
(entry) {
final index = entry.key;
final subSpace = entry.value;
final lowerName = subSpace.subspaceName.toLowerCase();
final duplicateIndices = state.subSpaces
.asMap()
.entries
.where((e) =>
e.value.subspaceName.toLowerCase() == lowerName)
.map((e) => e.key)
.toList();
final isDuplicate = duplicateIndices.length > 1 &&
duplicateIndices.indexOf(index) != 0;
return SubspaceChip(
subSpace: SubspaceTemplateModel(
subspaceName: entry.value.subspaceName,
disabled: entry.value.disabled,
),
isDuplicate: isDuplicate,
onDeleted: () => context.read<SubSpaceBloc>().add(
RemoveSubSpace(subSpace),
),
);
},
),
SizedBox(
width: 200,
child: TextField(
controller: _subspaceNameController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: state.subSpaces.isEmpty
? 'Please enter the name'
: null,
hintStyle: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.lightGrayColor,
),
),
onSubmitted: (value) {
final trimmedValue = value.trim();
if (trimmedValue.isNotEmpty) {
context.read<SubSpaceBloc>().add(
AddSubSpace(
SubspaceModel(
subspaceName: trimmedValue,
disabled: false,
),
),
);
_subspaceNameController.clear();
}
},
style: context.textTheme.bodyMedium,
),
),
],
),
), ),
if (state.errorMessage.isNotEmpty) if (state.errorMessage.isNotEmpty)
Padding( Padding(
@@ -104,10 +168,36 @@ class _CreateSubSpaceDialogState extends State<CreateSubSpaceDialog> {
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
OkCancelSubSpaceWidget( Row(
subspaceNameController: _subspaceNameController, children: [
errorMessage: state.errorMessage, Expanded(
onSave: widget.onSave, child: CancelButton(
label: 'Cancel',
onPressed: () async {
Navigator.of(context).pop();
},
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
onPressed: state.errorMessage.isEmpty
? () {
final subSpacesBloc = context.read<SubSpaceBloc>();
final subSpaces = subSpacesBloc.state.subSpaces;
widget.onSave?.call(subSpaces);
Navigator.of(context).pop();
}
: null,
backgroundColor: ColorsManager.secondaryColor,
borderRadius: 10,
foregroundColor: state.errorMessage.isNotEmpty
? ColorsManager.whiteColorsWithOpacity
: ColorsManager.whiteColors,
child: const Text('OK'),
),
),
],
), ),
], ],
), ),
@@ -1,75 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import '../../bloc/subspace_bloc.dart';
import '../../bloc/subspace_event.dart';
class OkCancelSubSpaceWidget extends StatelessWidget {
const OkCancelSubSpaceWidget({
super.key,
required this.subspaceNameController,
required this.onSave,
required this.errorMessage,
});
final TextEditingController subspaceNameController;
final void Function(
List<SubspaceModel>?, List<UpdateSubspaceModel> updatedSubSpaces)? onSave;
final String errorMessage;
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: CancelButton(
label: 'Cancel',
onPressed: () async {
Navigator.of(context).pop();
},
),
),
const SizedBox(width: 10),
Expanded(
child: DefaultButton(
onPressed: errorMessage.isEmpty
? () async {
final trimmedValue = subspaceNameController.text.trim();
final subSpacesBloc = context.read<SubSpaceBloc>();
if (trimmedValue.isNotEmpty) {
subSpacesBloc.add(
AddSubSpace(
SubspaceModel(
subspaceName: trimmedValue,
disabled: false,
),
),
);
subspaceNameController.clear();
}
await Future.delayed(const Duration(milliseconds: 10));
final subSpaces = subSpacesBloc.state.subSpaces;
onSave?.call(
subSpaces, subSpacesBloc.state.updatedSubSpaceModels);
Navigator.of(context).pop();
}
: null,
backgroundColor: ColorsManager.secondaryColor,
borderRadius: 10,
foregroundColor: errorMessage.isNotEmpty
? ColorsManager.whiteColorsWithOpacity
: ColorsManager.whiteColors,
child: const Text('OK'),
),
),
],
);
}
}
@@ -1,98 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import '../../../../../utils/color_manager.dart';
import '../../../all_spaces/model/subspace_model.dart';
import '../../../create_subspace_model/widgets/subspace_chip.dart';
import '../../../space_model/models/subspace_template_model.dart';
import '../../bloc/subspace_bloc.dart';
import '../../bloc/subspace_event.dart';
class TextFieldSubSpaceDialogWidget extends StatelessWidget {
const TextFieldSubSpaceDialogWidget({
super.key,
required TextEditingController subspaceNameController,
required this.subSpaces,
}) : _subspaceNameController = subspaceNameController;
final TextEditingController _subspaceNameController;
final List<SubspaceModel> subSpaces;
@override
Widget build(BuildContext context) {
return Container(
width: context.screenWidth * 0.35,
padding: const EdgeInsets.symmetric(
vertical: 10,
horizontal: 16,
),
decoration: BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(10),
),
child: Wrap(
spacing: 8,
runSpacing: 8,
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
...subSpaces.asMap().entries.map(
(entry) {
final index = entry.key;
final subSpace = entry.value;
final lowerName = subSpace.subspaceName.toLowerCase();
final duplicateIndices = subSpaces
.asMap()
.entries
.where((e) => e.value.subspaceName.toLowerCase() == lowerName)
.map((e) => e.key)
.toList();
final isDuplicate = duplicateIndices.length > 1 &&
duplicateIndices.indexOf(index) != 0;
return SubspaceChip(
subSpace: SubspaceTemplateModel(
subspaceName: entry.value.subspaceName,
disabled: entry.value.disabled,
),
isDuplicate: isDuplicate,
onDeleted: () => context.read<SubSpaceBloc>().add(
RemoveSubSpace(subSpace),
),
);
},
),
SizedBox(
width: 200,
child: TextField(
controller: _subspaceNameController,
decoration: InputDecoration(
border: InputBorder.none,
hintText: subSpaces.isEmpty ? 'Please enter the name' : null,
hintStyle: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.lightGrayColor,
),
),
onSubmitted: (value) {
final trimmedValue = value.trim();
if (trimmedValue.isNotEmpty) {
context.read<SubSpaceBloc>().add(
AddSubSpace(
SubspaceModel(
subspaceName: trimmedValue,
disabled: false,
),
),
);
_subspaceNameController.clear();
}
},
style: context.textTheme.bodyMedium,
),
),
],
),
);
}
}
@@ -14,11 +14,11 @@ class CenterBodyWidget extends StatelessWidget {
if (state is InitialState) { if (state is InitialState) {
context.read<CenterBodyBloc>().add(CommunityStructureSelectedEvent()); context.read<CenterBodyBloc>().add(CommunityStructureSelectedEvent());
} }
if (state is CommunityStructureState) { if (state is CommunityStructureState) {
context.read<SpaceManagementBloc>().add(BlankStateEvent(context)); context.read<SpaceManagementBloc>().add(BlankStateEvent(context));
} }
if (state is SpaceModelState) { if (state is SpaceModelState) {
context.read<SpaceManagementBloc>().add(SpaceModelLoadEvent(context)); context.read<SpaceManagementBloc>().add(SpaceModelLoadEvent(context));
} }
@@ -31,19 +31,15 @@ class CenterBodyWidget extends StatelessWidget {
children: [ children: [
GestureDetector( GestureDetector(
onTap: () { onTap: () {
context context.read<CenterBodyBloc>().add(CommunityStructureSelectedEvent());
.read<CenterBodyBloc>()
.add(CommunityStructureSelectedEvent());
}, },
child: Text( child: Text(
'Community Structure', 'Community Structure',
style: Theme.of(context).textTheme.bodyLarge!.copyWith( style: Theme.of(context).textTheme.bodyLarge!.copyWith(
fontWeight: state is CommunityStructureState || fontWeight: state is CommunityStructureState || state is CommunitySelectedState
state is CommunitySelectedState
? FontWeight.bold ? FontWeight.bold
: FontWeight.normal, : FontWeight.normal,
color: state is CommunityStructureState || color: state is CommunityStructureState || state is CommunitySelectedState
state is CommunitySelectedState
? Theme.of(context).textTheme.bodyLarge!.color ? Theme.of(context).textTheme.bodyLarge!.color
: Theme.of(context) : Theme.of(context)
.textTheme .textTheme
@@ -54,26 +50,26 @@ class CenterBodyWidget extends StatelessWidget {
), ),
), ),
const SizedBox(width: 20), const SizedBox(width: 20),
// GestureDetector( GestureDetector(
// onTap: () { onTap: () {
// context.read<CenterBodyBloc>().add(SpaceModelSelectedEvent()); context.read<CenterBodyBloc>().add(SpaceModelSelectedEvent());
// }, },
// child: Text( child: Text(
// 'Space Model', 'Space Model',
// style: Theme.of(context).textTheme.bodyLarge!.copyWith( style: Theme.of(context).textTheme.bodyLarge!.copyWith(
// fontWeight: state is SpaceModelState fontWeight: state is SpaceModelState
// ? FontWeight.bold ? FontWeight.bold
// : FontWeight.normal, : FontWeight.normal,
// color: state is SpaceModelState color: state is SpaceModelState
// ? Theme.of(context).textTheme.bodyLarge!.color ? Theme.of(context).textTheme.bodyLarge!.color
// : Theme.of(context) : Theme.of(context)
// .textTheme .textTheme
// .bodyLarge! .bodyLarge!
// .color! .color!
// .withOpacity(0.5), .withOpacity(0.5),
// ), ),
// ), ),
// ), ),
], ],
), ),
], ],
@@ -24,22 +24,37 @@ class AddDeviceTypeModelBloc
if (currentState is AddDeviceModelLoaded) { if (currentState is AddDeviceModelLoaded) {
final existingProduct = currentState.selectedProducts.firstWhere( final existingProduct = currentState.selectedProducts.firstWhere(
(p) => p.productId == event.selectedProduct.productId, (p) => p.productId == event.productId,
orElse: () => event.selectedProduct, orElse: () => SelectedProduct(
productId: event.productId,
count: 0,
productName: event.productName,
product: event.product,
),
); );
List<SelectedProduct> updatedProducts; List<SelectedProduct> updatedProducts;
if (event.selectedProduct.count > 0) { if (event.count > 0) {
if (!currentState.selectedProducts.contains(existingProduct)) { if (!currentState.selectedProducts.contains(existingProduct)) {
updatedProducts = [ updatedProducts = [
...currentState.selectedProducts, ...currentState.selectedProducts,
event.selectedProduct, SelectedProduct(
productId: event.productId,
count: event.count,
productName: event.productName,
product: event.product,
),
]; ];
} else { } else {
updatedProducts = currentState.selectedProducts.map((p) { updatedProducts = currentState.selectedProducts.map((p) {
if (p.productId == event.selectedProduct.productId) { if (p.productId == event.productId) {
return event.selectedProduct; return SelectedProduct(
productId: p.productId,
count: event.count,
productName: p.productName,
product: p.product,
);
} }
return p; return p;
}).toList(); }).toList();
@@ -47,7 +62,7 @@ class AddDeviceTypeModelBloc
} else { } else {
// Remove the product if the count is 0 // Remove the product if the count is 0
updatedProducts = currentState.selectedProducts updatedProducts = currentState.selectedProducts
.where((p) => p.productId != event.selectedProduct.productId) .where((p) => p.productId != event.productId)
.toList(); .toList();
} }
@@ -10,15 +10,20 @@ abstract class AddDeviceTypeModelEvent extends Equatable {
List<Object> get props => []; List<Object> get props => [];
} }
class UpdateProductCountEvent extends AddDeviceTypeModelEvent {
final SelectedProduct selectedProduct;
UpdateProductCountEvent({required this.selectedProduct}); class UpdateProductCountEvent extends AddDeviceTypeModelEvent {
final String productId;
final int count;
final String productName;
final ProductModel product;
UpdateProductCountEvent({required this.productId, required this.count, required this.productName, required this.product});
@override @override
List<Object> get props => [selectedProduct]; List<Object> get props => [productId, count];
} }
class InitializeDeviceTypeModel extends AddDeviceTypeModelEvent { class InitializeDeviceTypeModel extends AddDeviceTypeModelEvent {
final List<Tag> initialTags; final List<Tag> initialTags;
final List<SelectedProduct> addedProducts; final List<SelectedProduct> addedProducts;
@@ -54,10 +54,10 @@ class DeviceTypeTileWidget extends StatelessWidget {
onCountChanged: (newCount) { onCountChanged: (newCount) {
context.read<AddDeviceTypeModelBloc>().add( context.read<AddDeviceTypeModelBloc>().add(
UpdateProductCountEvent( UpdateProductCountEvent(
selectedProduct: product.toSelectedProduct( productId: product.uuid,
newCount, count: newCount,
), productName: product.catName,
), product: product),
); );
}, },
), ),
@@ -38,7 +38,7 @@ class VisitorPasswordDialog extends StatelessWidget {
if (visitorBloc.passwordStatus!.failedOperations.isNotEmpty) if (visitorBloc.passwordStatus!.failedOperations.isNotEmpty)
Column( Column(
children: [ children: [
const Text('Failed Devises'), const Text('Failed Devices'),
SizedBox( SizedBox(
width: 200, width: 200,
height: 50, height: 50,
@@ -63,7 +63,7 @@ class VisitorPasswordDialog extends StatelessWidget {
if (visitorBloc.passwordStatus!.successOperations.isNotEmpty) if (visitorBloc.passwordStatus!.successOperations.isNotEmpty)
Column( Column(
children: [ children: [
const Text('Success Devises'), const Text('Success Devices'),
SizedBox( SizedBox(
width: 200, width: 200,
height: 50, height: 50,
-12
View File
@@ -22,18 +22,6 @@ class HTTPService {
); );
client.interceptors.add(serviceLocator.get<HTTPInterceptor>()); client.interceptors.add(serviceLocator.get<HTTPInterceptor>());
// Add this interceptor for logging requests and responses
// client.interceptors.add(
// LogInterceptor(
// request: true,
// requestHeader: true,
// requestBody: true,
// responseHeader: false,
// responseBody: true,
// error: true,
// logPrint: (object) => print(object),
// ),
// );
return client; return client;
} }
+24 -44
View File
@@ -4,27 +4,22 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_m
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';
import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart'; import 'package:syncrow_web/utils/constants/api_const.dart';
import '../pages/spaces_management/all_spaces/model/subspace_model.dart';
class CommunitySpaceManagementApi { class CommunitySpaceManagementApi {
// Community Management APIs // Community Management APIs
Future<List<CommunityModel>> fetchCommunities(String projectId, Future<List<CommunityModel>> fetchCommunities(String projectId, {int page = 1}) async {
{int page = 1}) async {
try { try {
List<CommunityModel> allCommunities = []; List<CommunityModel> allCommunities = [];
bool hasNext = true; bool hasNext = true;
while (hasNext) { while (hasNext) {
await HTTPService().get( await HTTPService().get(
path: ApiEndpoints.getCommunityList path: ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId),
.replaceAll('{projectId}', projectId),
queryParameters: { queryParameters: {
'page': page, 'page': page,
}, },
@@ -60,14 +55,8 @@ class CommunitySpaceManagementApi {
try { try {
bool hasNext = false; bool hasNext = false;
await HTTPService().get( await HTTPService().get(
path: path: ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId),
ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId), queryParameters: {'page': page, 'includeSpaces': true, 'size': 25, 'search': search},
queryParameters: {
'page': page,
'includeSpaces': true,
'size': 25,
'search': search
},
expectedResponseModel: (json) { expectedResponseModel: (json) {
try { try {
List<dynamic> jsonData = json['data'] ?? []; List<dynamic> jsonData = json['data'] ?? [];
@@ -79,10 +68,7 @@ class CommunitySpaceManagementApi {
page = currentPage + 1; page = currentPage + 1;
paginationModel = PaginationModel( paginationModel = PaginationModel(
pageNum: page, pageNum: page, hasNext: hasNext, size: 25, communities: communityList);
hasNext: hasNext,
size: 25,
communities: communityList);
return paginationModel; return paginationModel;
} catch (_) { } catch (_) {
hasNext = false; hasNext = false;
@@ -97,8 +83,7 @@ class CommunitySpaceManagementApi {
Future<CommunityModel?> getCommunityById(String communityId) async { Future<CommunityModel?> getCommunityById(String communityId) async {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getCommunityById path: ApiEndpoints.getCommunityById.replaceAll('{communityId}', communityId),
.replaceAll('{communityId}', communityId),
expectedResponseModel: (json) { expectedResponseModel: (json) {
return CommunityModel.fromJson(json['data']); return CommunityModel.fromJson(json['data']);
}, },
@@ -110,8 +95,7 @@ class CommunitySpaceManagementApi {
} }
} }
Future<CommunityModel?> createCommunity( Future<CommunityModel?> createCommunity(String name, String description, String projectId) async {
String name, String description, String projectId) async {
try { try {
final response = await HTTPService().post( final response = await HTTPService().post(
path: ApiEndpoints.createCommunity.replaceAll('{projectId}', projectId), path: ApiEndpoints.createCommunity.replaceAll('{projectId}', projectId),
@@ -130,8 +114,7 @@ class CommunitySpaceManagementApi {
} }
} }
Future<bool> updateCommunity( Future<bool> updateCommunity(String communityId, String name, String projectId) async {
String communityId, String name, String projectId) async {
try { try {
final response = await HTTPService().put( final response = await HTTPService().put(
path: ApiEndpoints.updateCommunity path: ApiEndpoints.updateCommunity
@@ -168,8 +151,7 @@ class CommunitySpaceManagementApi {
} }
} }
Future<SpacesResponse> fetchSpaces( Future<SpacesResponse> fetchSpaces(String communityId, String projectId) async {
String communityId, String projectId) async {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.listSpaces path: ApiEndpoints.listSpaces
@@ -195,8 +177,7 @@ class CommunitySpaceManagementApi {
} }
} }
Future<SpaceModel?> getSpace( Future<SpaceModel?> getSpace(String communityId, String spaceId, String projectId) async {
String communityId, String spaceId, String projectId) async {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getSpace path: ApiEndpoints.getSpace
@@ -218,6 +199,7 @@ class CommunitySpaceManagementApi {
{required String communityId, {required String communityId,
required String name, required String name,
String? parentId, String? parentId,
String? direction,
bool isPrivate = false, bool isPrivate = false,
required Offset position, required Offset position,
String? spaceModelUuid, String? spaceModelUuid,
@@ -231,6 +213,7 @@ class CommunitySpaceManagementApi {
'isPrivate': isPrivate, 'isPrivate': isPrivate,
'x': position.dx, 'x': position.dx,
'y': position.dy, 'y': position.dy,
'direction': direction,
'icon': icon, 'icon': icon,
}; };
if (parentId != null) { if (parentId != null) {
@@ -265,10 +248,11 @@ class CommunitySpaceManagementApi {
required String name, required String name,
String? parentId, String? parentId,
String? icon, String? icon,
String? direction,
bool isPrivate = false, bool isPrivate = false,
required Offset position, required Offset position,
List<Tag>? tags, List<TagModelUpdate>? tags,
List<SubspaceModel>? subspaces, List<UpdateSubspaceTemplateModel>? subspaces,
String? spaceModelUuid, String? spaceModelUuid,
required String projectId}) async { required String projectId}) async {
try { try {
@@ -277,8 +261,9 @@ class CommunitySpaceManagementApi {
'isPrivate': isPrivate, 'isPrivate': isPrivate,
'x': position.dx, 'x': position.dx,
'y': position.dy, 'y': position.dy,
'direction': direction,
'icon': icon, 'icon': icon,
'subspaces': subspaces, 'subspace': subspaces,
'tags': tags, 'tags': tags,
'spaceModelUuid': spaceModelUuid, 'spaceModelUuid': spaceModelUuid,
}; };
@@ -304,8 +289,7 @@ class CommunitySpaceManagementApi {
} }
} }
Future<bool> deleteSpace( Future<bool> deleteSpace(String communityId, String spaceId, String projectId) async {
String communityId, String spaceId, String projectId) async {
try { try {
final response = await HTTPService().delete( final response = await HTTPService().delete(
path: ApiEndpoints.deleteSpace path: ApiEndpoints.deleteSpace
@@ -323,17 +307,15 @@ class CommunitySpaceManagementApi {
} }
} }
Future<List<SpaceModel>> getSpaceHierarchy( Future<List<SpaceModel>> getSpaceHierarchy(String communityId, String projectId) async {
String communityId, String projectId) async {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getSpaceHierarchy path: ApiEndpoints.getSpaceHierarchy
.replaceAll('{communityId}', communityId) .replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', projectId), .replaceAll('{projectId}', projectId),
expectedResponseModel: (json) { expectedResponseModel: (json) {
final spaceModels = (json['data'] as List) final spaceModels =
.map((spaceJson) => SpaceModel.fromJson(spaceJson)) (json['data'] as List).map((spaceJson) => SpaceModel.fromJson(spaceJson)).toList();
.toList();
return spaceModels; return spaceModels;
}, },
@@ -345,17 +327,15 @@ class CommunitySpaceManagementApi {
} }
} }
Future<List<SpaceModel>> getSpaceOnlyWithDevices( Future<List<SpaceModel>> getSpaceOnlyWithDevices({String? communityId, String? projectId}) async {
{String? communityId, String? projectId}) async {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.spaceOnlyWithDevices path: ApiEndpoints.spaceOnlyWithDevices
.replaceAll('{communityId}', communityId!) .replaceAll('{communityId}', communityId!)
.replaceAll('{projectId}', projectId!), .replaceAll('{projectId}', projectId!),
expectedResponseModel: (json) { expectedResponseModel: (json) {
final spaceModels = (json['data'] as List) final spaceModels =
.map((spaceJson) => SpaceModel.fromJson(spaceJson)) (json['data'] as List).map((spaceJson) => SpaceModel.fromJson(spaceJson)).toList();
.toList();
return spaceModels; return spaceModels;
}, },
); );
+14 -23
View File
@@ -1,5 +1,3 @@
import 'dart:developer';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
@@ -9,23 +7,17 @@ import 'package:syncrow_web/utils/constants/api_const.dart';
class SpaceModelManagementApi { class SpaceModelManagementApi {
Future<List<SpaceTemplateModel>> listSpaceModels( Future<List<SpaceTemplateModel>> listSpaceModels(
{required String projectId, int page = 1}) async { {required String projectId, int page = 1}) async {
try { final response = await HTTPService().get(
// final response = await HTTPService().get( path: ApiEndpoints.listSpaceModels.replaceAll('{projectId}', projectId),
// path: ApiEndpoints.listSpaceModels.replaceAll('{projectId}', projectId), queryParameters: {'page': page},
// queryParameters: {'page': page}, expectedResponseModel: (json) {
// expectedResponseModel: (json) { List<dynamic> jsonData = json['data'];
// List<dynamic> jsonData = json['data']; return jsonData.map((jsonItem) {
// return jsonData.map((jsonItem) { return SpaceTemplateModel.fromJson(jsonItem);
// return SpaceTemplateModel.fromJson(jsonItem); }).toList();
// }).toList(); },
// }, );
// ); return response;
return [];
// response;
} catch (e) {
log(e.toString());
return [];
}
} }
Future<SpaceTemplateModel?> createSpaceModel( Future<SpaceTemplateModel?> createSpaceModel(
@@ -41,8 +33,8 @@ class SpaceModelManagementApi {
return response; return response;
} }
Future<String?> updateSpaceModel(CreateSpaceTemplateBodyModel spaceModel, Future<String?> updateSpaceModel(
String spaceModelUuid, String projectId) async { CreateSpaceTemplateBodyModel spaceModel, String spaceModelUuid, String projectId) async {
final response = await HTTPService().put( final response = await HTTPService().put(
path: ApiEndpoints.updateSpaceModel path: ApiEndpoints.updateSpaceModel
.replaceAll('{projectId}', projectId) .replaceAll('{projectId}', projectId)
@@ -55,8 +47,7 @@ class SpaceModelManagementApi {
return response; return response;
} }
Future<SpaceTemplateModel?> getSpaceModel( Future<SpaceTemplateModel?> getSpaceModel(String spaceModelUuid, String projectId) async {
String spaceModelUuid, String projectId) async {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getSpaceModel path: ApiEndpoints.getSpaceModel
.replaceAll('{projectId}', projectId) .replaceAll('{projectId}', projectId)