Merge branch 'feat/update-space-model' of https://github.com/SyncrowIOT/web into dev

This commit is contained in:
hannathkadher
2025-03-09 10:26:34 +04:00
66 changed files with 1971 additions and 957 deletions

View File

@ -1,35 +1,38 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class DialogTextfieldDropdown extends StatefulWidget {
final List<String> items;
final ValueChanged<String> onSelected;
final String? initialValue;
class TagDialogTextfieldDropdown extends StatefulWidget {
final List<Tag> items;
final ValueChanged<Tag> onSelected;
final Tag? initialValue;
final String product;
const DialogTextfieldDropdown({
const TagDialogTextfieldDropdown({
Key? key,
required this.items,
required this.onSelected,
this.initialValue,
required this.product,
}) : super(key: key);
@override
_DialogTextfieldDropdownState createState() =>
_DialogTextfieldDropdownState();
_DialogTextfieldDropdownState createState() => _DialogTextfieldDropdownState();
}
class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
bool _isOpen = false;
OverlayEntry? _overlayEntry;
final TextEditingController _controller = TextEditingController();
final FocusNode _focusNode = FocusNode();
List<String> _filteredItems = [];
List<Tag> _filteredItems = [];
@override
void initState() {
super.initState();
_controller.text = widget.initialValue ?? '';
_filteredItems = List.from(widget.items);
_controller.text = widget.initialValue?.tag ?? '';
_filterItems();
_focusNode.addListener(() {
if (!_focusNode.hasFocus) {
@ -38,6 +41,12 @@ class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
});
}
void _filterItems() {
setState(() {
_filteredItems = widget.items.where((tag) => tag.product?.uuid == widget.product).toList();
});
}
void _toggleDropdown() {
if (_isOpen) {
_closeDropdown();
@ -87,7 +96,7 @@ class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
shrinkWrap: true,
itemCount: _filteredItems.length,
itemBuilder: (context, index) {
final item = _filteredItems[index];
final tag = _filteredItems[index];
return Container(
decoration: const BoxDecoration(
@ -99,19 +108,16 @@ class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
),
),
child: ListTile(
title: Text(item,
title: Text(tag.tag ?? '',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
color: ColorsManager
.textPrimaryColor)),
?.copyWith(color: ColorsManager.textPrimaryColor)),
onTap: () {
_controller.text = item;
widget.onSelected(item);
_controller.text = tag.tag ?? '';
widget.onSelected(tag);
setState(() {
_filteredItems
.remove(item); // Remove selected item
_filteredItems.remove(tag);
});
_closeDropdown();
},
@ -150,11 +156,14 @@ class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
controller: _controller,
focusNode: _focusNode,
onFieldSubmitted: (value) {
widget.onSelected(value);
final selectedTag = _filteredItems.firstWhere((tag) => tag.tag == value,
orElse: () => Tag(tag: value));
widget.onSelected(selectedTag);
_closeDropdown();
},
onTapOutside: (event) {
widget.onSelected(_controller.text);
widget.onSelected(_filteredItems.firstWhere((tag) => tag.tag == _controller.text,
orElse: () => Tag(tag: _controller.text)));
_closeDropdown();
},
style: Theme.of(context).textTheme.bodyMedium,

View File

@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:go_router/go_router.dart';
import 'package:syncrow_web/firebase_options_dev.dart';
import 'package:syncrow_web/firebase_options_prod.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';

View File

@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_state.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart';
import 'package:syncrow_web/pages/common/custom_table.dart';

View File

@ -15,7 +15,6 @@ import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/services/auth_api.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
import 'package:syncrow_web/utils/snack_bar.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {

View File

@ -5,9 +5,6 @@ import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
part 'device_managment_event.dart';
part 'device_managment_state.dart';

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';

View File

@ -20,7 +20,6 @@ class TreeView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
debugPrint('TreeView constructed with userId = $userId');
return BlocProvider(
create: (_) => UsersBloc(),
// ..add(const LoadCommunityAndSpacesEvent()),

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/common/widgets/search_bar.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';
@ -8,11 +9,14 @@ import 'package:syncrow_web/pages/space_tree/view/custom_expansion.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';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class SpaceTreeView extends StatefulWidget {
final bool? isSide;
final Function onSelect;
const SpaceTreeView({required this.onSelect, super.key});
const SpaceTreeView({required this.onSelect, this.isSide, super.key});
@override
State<SpaceTreeView> createState() => _SpaceTreeViewState();
@ -29,21 +33,77 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
@override
Widget build(BuildContext context) {
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
List<CommunityModel> list = state.isSearching ? state.filteredCommunity : state.communityList;
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(
builder: (context, state) {
List<CommunityModel> list =
state.isSearching ? state.filteredCommunity : state.communityList;
return Container(
height: MediaQuery.sizeOf(context).height,
decoration: subSectionContainerDecoration,
decoration:
widget.isSide == true ? subSectionContainerDecoration : null,
child: state is SpaceTreeLoadingState
? const Center(child: CircularProgressIndicator())
: Column(
children: [
CustomSearchBar(
searchQuery: state.searchQuery,
onSearchChanged: (query) {
context.read<SpaceTreeBloc>().add(SearchQueryEvent(query));
},
),
widget.isSide == true
? Container(
decoration: const BoxDecoration(
color: ColorsManager.circleRolesBackground,
borderRadius: BorderRadius.only(
topRight: Radius.circular(20),
topLeft: Radius.circular(20)),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(20)),
border: Border.all(
color: ColorsManager.grayBorder)),
child: TextFormField(
style:
const TextStyle(color: Colors.black),
onChanged: (value) {
context
.read<SpaceTreeBloc>()
.add(SearchQueryEvent(value));
},
decoration: textBoxDecoration(radios: 20)!
.copyWith(
fillColor: Colors.white,
suffixIcon: Padding(
padding:
const EdgeInsets.only(right: 16),
child: SvgPicture.asset(
Assets.textFieldSearch,
width: 24,
height: 24,
),
),
hintStyle: context.textTheme.bodyMedium
?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray),
),
),
),
),
],
),
),
)
: CustomSearchBar(
onSearchChanged: (query) {
context
.read<SpaceTreeBloc>()
.add(SearchQueryEvent(query));
},
),
const SizedBox(height: 16),
Expanded(
child: ListView(
@ -57,14 +117,18 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
? Center(
child: Text(
'No results found',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.lightGrayColor,
fontWeight: FontWeight.w400,
),
),
)
: Scrollbar(
scrollbarOrientation: ScrollbarOrientation.left,
scrollbarOrientation:
ScrollbarOrientation.left,
thumbVisibility: true,
controller: _scrollController,
child: Padding(
@ -74,30 +138,39 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
shrinkWrap: true,
children: list
.map(
(community) => CustomExpansionTileSpaceTree(
(community) =>
CustomExpansionTileSpaceTree(
title: community.name,
isSelected: state.selectedCommunities
isSelected: state
.selectedCommunities
.contains(community.uuid),
isSoldCheck: state.selectedCommunities
isSoldCheck: state
.selectedCommunities
.contains(community.uuid),
onExpansionChanged: () {
context
.read<SpaceTreeBloc>()
.add(OnCommunityExpanded(community.uuid));
.add(OnCommunityExpanded(
community.uuid));
},
isExpanded: state.expandedCommunities
isExpanded: state
.expandedCommunities
.contains(community.uuid),
onItemSelected: () {
context.read<SpaceTreeBloc>().add(
OnCommunitySelected(
community.uuid, community.spaces));
context
.read<SpaceTreeBloc>()
.add(OnCommunitySelected(
community.uuid,
community.spaces));
widget.onSelect();
},
children: community.spaces.map((space) {
children:
community.spaces.map((space) {
return CustomExpansionTileSpaceTree(
title: space.name,
isExpanded:
state.expandedSpaces.contains(space.uuid),
isExpanded: state
.expandedSpaces
.contains(space.uuid),
onItemSelected: () {
context.read<SpaceTreeBloc>().add(
OnSpaceSelected(community, space.uuid ?? '',
@ -105,14 +178,20 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
widget.onSelect();
},
onExpansionChanged: () {
context.read<SpaceTreeBloc>().add(
OnSpaceExpanded(
community.uuid, space.uuid ?? ''));
context
.read<SpaceTreeBloc>()
.add(OnSpaceExpanded(
community.uuid,
space.uuid ?? ''));
},
isSelected:
state.selectedSpaces.contains(space.uuid) ||
state.soldCheck.contains(space.uuid),
isSoldCheck: state.soldCheck.contains(space.uuid),
isSelected: state
.selectedSpaces
.contains(
space.uuid) ||
state.soldCheck
.contains(space.uuid),
isSoldCheck: state.soldCheck
.contains(space.uuid),
children: _buildNestedSpaces(
context, state, space, community),
);
@ -200,8 +279,8 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
BuildContext context, SpaceTreeState state, SpaceModel space, CommunityModel community) {
return space.children.map((child) {
return CustomExpansionTileSpaceTree(
isSelected:
state.selectedSpaces.contains(child.uuid) || state.soldCheck.contains(child.uuid),
isSelected: state.selectedSpaces.contains(child.uuid) ||
state.soldCheck.contains(child.uuid),
isSoldCheck: state.soldCheck.contains(child.uuid),
title: child.name,
isExpanded: state.expandedSpaces.contains(child.uuid),

View File

@ -24,6 +24,8 @@ class AddDeviceTypeWidget extends StatelessWidget {
final String spaceName;
final bool isCreate;
final Function(List<Tag>, List<SubspaceModel>?)? onSave;
final List<Tag> projectTags;
const AddDeviceTypeWidget(
{super.key,
@ -35,7 +37,8 @@ class AddDeviceTypeWidget extends StatelessWidget {
this.allTags,
this.spaceTags,
this.onSave,
required this.spaceName});
required this.spaceName,
required this.projectTags});
@override
Widget build(BuildContext context) {
@ -134,7 +137,8 @@ class AddDeviceTypeWidget extends StatelessWidget {
spaceName: spaceName,
initialTags: initialTags,
title: dialogTitle,
onSave: onSave),
onSave: onSave,
projectTags: projectTags),
);
}
},

View File

@ -19,8 +19,7 @@ import 'package:syncrow_web/services/space_mana_api.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart' as custom_action;
class SpaceManagementBloc
extends Bloc<SpaceManagementEvent, SpaceManagementState> {
class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementState> {
final CommunitySpaceManagementApi _api;
final ProductApi _productApi;
final SpaceModelManagementApi _spaceModelApi;
@ -28,6 +27,7 @@ class SpaceManagementBloc
List<ProductModel>? _cachedProducts;
List<SpaceTemplateModel>? _cachedSpaceModels;
final SpaceTreeBloc _spaceTreeBloc;
List<Tag>? _cachedTags;
SpaceManagementBloc(
this._api,
@ -54,40 +54,38 @@ class SpaceManagementBloc
UpdateSpaceModelCache event, Emitter<SpaceManagementState> emit) async {
if (_cachedSpaceModels != null) {
_cachedSpaceModels = _cachedSpaceModels!.map((model) {
return model.uuid == event.updatedModel.uuid
? event.updatedModel
: model;
return model.uuid == event.updatedModel.uuid ? event.updatedModel : model;
}).toList();
} else {
_cachedSpaceModels = await fetchSpaceModels();
}
await fetchTags();
emit(SpaceModelLoaded(
communities: state is SpaceManagementLoaded
? (state as SpaceManagementLoaded).communities
: [],
products: _cachedProducts ?? [],
spaceModels: List.from(_cachedSpaceModels ?? []),
));
communities:
state is SpaceManagementLoaded ? (state as SpaceManagementLoaded).communities : [],
products: _cachedProducts ?? [],
spaceModels: List.from(_cachedSpaceModels ?? []),
allTags: _cachedTags ?? []));
}
void _deleteSpaceModelFromCache(DeleteSpaceModelFromCache event,
Emitter<SpaceManagementState> emit) async {
void _deleteSpaceModelFromCache(
DeleteSpaceModelFromCache event, Emitter<SpaceManagementState> emit) async {
if (_cachedSpaceModels != null) {
_cachedSpaceModels = _cachedSpaceModels!
.where((model) => model.uuid != event.deletedUuid)
.toList();
_cachedSpaceModels =
_cachedSpaceModels!.where((model) => model.uuid != event.deletedUuid).toList();
} else {
_cachedSpaceModels = await fetchSpaceModels();
}
await fetchTags();
emit(SpaceModelLoaded(
communities: state is SpaceManagementLoaded
? (state as SpaceManagementLoaded).communities
: [],
products: _cachedProducts ?? [],
spaceModels: List.from(_cachedSpaceModels ?? []),
));
communities:
state is SpaceManagementLoaded ? (state as SpaceManagementLoaded).communities : [],
products: _cachedProducts ?? [],
spaceModels: List.from(_cachedSpaceModels ?? []),
allTags: _cachedTags ?? []));
}
void updateCachedSpaceModels(List<SpaceTemplateModel> updatedModels) {
@ -112,8 +110,8 @@ class SpaceManagementBloc
int page = 1;
while (hasNext) {
final spaceModels = await _spaceModelApi.listSpaceModels(
page: page, projectId: projectUuid);
final spaceModels =
await _spaceModelApi.listSpaceModels(page: page, projectId: projectUuid);
if (spaceModels.isNotEmpty) {
allSpaceModels.addAll(spaceModels);
page++;
@ -130,6 +128,20 @@ class SpaceManagementBloc
}
}
Future<List<Tag>> fetchTags() async {
try {
if (_cachedTags != null) {
return _cachedTags!;
}
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
final allTags = await _spaceModelApi.listTags(projectId: projectUuid);
_cachedTags = allTags;
return allTags;
} catch (e) {
return [];
}
}
void _onUpdateCommunity(
UpdateCommunityEvent event,
Emitter<SpaceManagementState> emit,
@ -137,14 +149,13 @@ class SpaceManagementBloc
final previousState = state;
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
await fetchTags();
emit(SpaceManagementLoading());
final success = await _api.updateCommunity(
event.communityUuid, event.name, projectUuid);
final success = await _api.updateCommunity(event.communityUuid, event.name, projectUuid);
if (success) {
if (previousState is SpaceManagementLoaded) {
final updatedCommunities =
List<CommunityModel>.from(previousState.communities);
final updatedCommunities = List<CommunityModel>.from(previousState.communities);
for (var community in updatedCommunities) {
if (community.uuid == event.communityUuid) {
community.name = event.name;
@ -157,11 +168,11 @@ class SpaceManagementBloc
var prevSpaceModels = await fetchSpaceModels();
emit(SpaceManagementLoaded(
communities: updatedCommunities,
products: previousState.products,
selectedCommunity: previousState.selectedCommunity,
spaceModels: prevSpaceModels,
));
communities: updatedCommunities,
products: previousState.products,
selectedCommunity: previousState.selectedCommunity,
spaceModels: prevSpaceModels,
allTags: _cachedTags ?? []));
}
} else {
emit(const SpaceManagementError('Failed to update the community.'));
@ -189,8 +200,7 @@ class SpaceManagementBloc
}
}
Future<List<SpaceModel>> _fetchSpacesForCommunity(
String communityUuid) async {
Future<List<SpaceModel>> _fetchSpacesForCommunity(String communityUuid) async {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
return await _api.getSpaceHierarchy(communityUuid, projectUuid);
@ -201,7 +211,7 @@ class SpaceManagementBloc
Emitter<SpaceManagementState> emit,
) async {
try {
final previousState = state;
await fetchTags();
if (event.communities.isEmpty) {
emit(const SpaceManagementError('No communities provided.'));
@ -211,33 +221,33 @@ class SpaceManagementBloc
var prevSpaceModels = await fetchSpaceModels();
emit(BlankState(
communities: event.communities,
products: _cachedProducts ?? [],
spaceModels: prevSpaceModels,
));
communities: event.communities,
products: _cachedProducts ?? [],
spaceModels: prevSpaceModels,
allTags: _cachedTags ?? []));
} catch (error) {
emit(SpaceManagementError('Error loading communities: $error'));
}
}
Future<void> _onBlankState(
BlankStateEvent event, Emitter<SpaceManagementState> emit) async {
Future<void> _onBlankState(BlankStateEvent event, Emitter<SpaceManagementState> emit) async {
try {
final previousState = state;
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
var spaceBloc = event.context.read<SpaceTreeBloc>();
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc);
await fetchSpaceModels();
await fetchTags();
var prevSpaceModels = await fetchSpaceModels();
if (previousState is SpaceManagementLoaded ||
previousState is BlankState) {
if (previousState is SpaceManagementLoaded || previousState is BlankState) {
final prevCommunities = (previousState as dynamic).communities ?? [];
emit(BlankState(
communities: List<CommunityModel>.from(prevCommunities),
products: _cachedProducts ?? [],
spaceModels: prevSpaceModels,
));
communities: List<CommunityModel>.from(prevCommunities),
products: _cachedProducts ?? [],
spaceModels: prevSpaceModels,
allTags: _cachedTags ?? []));
return;
}
@ -246,8 +256,7 @@ class SpaceManagementBloc
List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async {
List<SpaceModel> spaces =
await _fetchSpacesForCommunity(community.uuid);
List<SpaceModel> spaces = await _fetchSpacesForCommunity(community.uuid);
return CommunityModel(
uuid: community.uuid,
createdAt: community.createdAt,
@ -267,6 +276,7 @@ class SpaceManagementBloc
spaceModels: prevSpaceModels,
communities: communities,
products: _cachedProducts ?? [],
allTags: _cachedTags ?? [],
));
} catch (error) {
emit(SpaceManagementError('Error loading communities: $error'));
@ -279,21 +289,20 @@ class SpaceManagementBloc
) async {
var spaceBloc = event.context.read<SpaceTreeBloc>();
_onloadProducts();
await fetchTags();
// Wait until `communityList` is loaded
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc);
// Fetch space models after communities are available
final prevSpaceModels = await fetchSpaceModels();
emit(SpaceManagementLoaded(
communities: communities,
products: _cachedProducts ?? [],
spaceModels: prevSpaceModels,
));
communities: communities,
products: _cachedProducts ?? [],
spaceModels: prevSpaceModels,
allTags: _cachedTags ?? []));
}
Future<List<CommunityModel>> _waitForCommunityList(
SpaceTreeBloc spaceBloc) async {
Future<List<CommunityModel>> _waitForCommunityList(SpaceTreeBloc spaceBloc) async {
// Check if communityList is already populated
if (spaceBloc.state.communityList.isNotEmpty) {
return spaceBloc.state.communityList;
@ -320,8 +329,7 @@ class SpaceManagementBloc
emit(SpaceManagementLoading());
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
final success =
await _api.deleteCommunity(event.communityUuid, projectUuid);
final success = await _api.deleteCommunity(event.communityUuid, projectUuid);
if (success) {
// add(LoadCommunityAndSpacesEvent());
} else {
@ -342,14 +350,13 @@ class SpaceManagementBloc
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
CommunityModel? newCommunity = await _api.createCommunity(
event.name, event.description, projectUuid);
await fetchTags();
CommunityModel? newCommunity =
await _api.createCommunity(event.name, event.description, projectUuid);
var prevSpaceModels = await fetchSpaceModels();
if (newCommunity != null) {
if (previousState is SpaceManagementLoaded ||
previousState is BlankState) {
if (previousState is SpaceManagementLoaded || previousState is BlankState) {
final prevCommunities = List<CommunityModel>.from(
(previousState as dynamic).communities,
);
@ -357,11 +364,13 @@ class SpaceManagementBloc
_spaceTreeBloc.add(OnCommunityAdded(newCommunity));
emit(SpaceManagementLoaded(
spaceModels: prevSpaceModels,
communities: updatedCommunities,
products: _cachedProducts ?? [],
selectedCommunity: newCommunity,
selectedSpace: null));
spaceModels: prevSpaceModels,
communities: updatedCommunities,
products: _cachedProducts ?? [],
selectedCommunity: newCommunity,
selectedSpace: null,
allTags: _cachedTags ?? [],
));
}
} else {
emit(const SpaceManagementError('Error creating community'));
@ -401,11 +410,12 @@ class SpaceManagementBloc
required Emitter<SpaceManagementState> emit,
CommunityModel? selectedCommunity,
SpaceModel? selectedSpace,
}) {
}) async {
final previousState = state;
emit(SpaceManagementLoading());
try {
await fetchTags();
if (previousState is SpaceManagementLoaded ||
previousState is BlankState ||
previousState is SpaceModelLoaded) {
@ -421,7 +431,8 @@ class SpaceManagementBloc
products: _cachedProducts ?? [],
selectedCommunity: selectedCommunity,
selectedSpace: selectedSpace,
spaceModels: spaceModels));
spaceModels: spaceModels,
allTags: _cachedTags ?? []));
}
} catch (e) {
emit(SpaceManagementError('Error updating state: $e'));
@ -436,8 +447,7 @@ class SpaceManagementBloc
emit(SpaceManagementLoading());
try {
final updatedSpaces =
await saveSpacesHierarchically(event.spaces, event.communityUuid);
final updatedSpaces = await saveSpacesHierarchically(event.spaces, event.communityUuid);
final allSpaces = await _fetchSpacesForCommunity(event.communityUuid);
@ -467,20 +477,21 @@ class SpaceManagementBloc
Emitter<SpaceManagementState> emit,
) async {
var prevSpaceModels = await fetchSpaceModels();
await fetchTags();
final communities = List<CommunityModel>.from(previousState.communities);
for (var community in communities) {
if (community.uuid == communityUuid) {
community.spaces = allSpaces;
_spaceTreeBloc.add(OnCommunityUpdated(community));
_spaceTreeBloc.add(InitialEvent());
emit(SpaceManagementLoaded(
communities: communities,
products: _cachedProducts ?? [],
selectedCommunity: community,
selectedSpace: null,
spaceModels: prevSpaceModels));
spaceModels: prevSpaceModels,
allTags: _cachedTags ?? []));
return;
}
}
@ -511,8 +522,7 @@ class SpaceManagementBloc
if (space.uuid != null && space.uuid!.isNotEmpty) {
List<TagModelUpdate> tagUpdates = [];
final prevSpace =
await _api.getSpace(communityUuid, space.uuid!, projectUuid);
final prevSpace = await _api.getSpace(communityUuid, space.uuid!, projectUuid);
final List<UpdateSubspaceTemplateModel> subspaceUpdates = [];
final List<SubspaceModel>? prevSubspaces = prevSpace?.subspaces;
final List<SubspaceModel>? newSubspaces = space.subspaces;
@ -522,19 +532,17 @@ class SpaceManagementBloc
if (prevSubspaces != null || newSubspaces != null) {
if (prevSubspaces != null && newSubspaces != null) {
for (var prevSubspace in prevSubspaces) {
final existsInNew = newSubspaces
.any((subspace) => subspace.uuid == prevSubspace.uuid);
final existsInNew =
newSubspaces.any((subspace) => subspace.uuid == prevSubspace.uuid);
if (!existsInNew) {
subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: custom_action.Action.delete,
uuid: prevSubspace.uuid));
action: custom_action.Action.delete, uuid: prevSubspace.uuid));
}
}
} else if (prevSubspaces != null && newSubspaces == null) {
for (var prevSubspace in prevSubspaces) {
subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: custom_action.Action.delete,
uuid: prevSubspace.uuid));
action: custom_action.Action.delete, uuid: prevSubspace.uuid));
}
}
@ -548,7 +556,7 @@ class SpaceManagementBloc
for (var tag in newSubspace.tags!) {
tagUpdates.add(TagModelUpdate(
action: custom_action.Action.add,
uuid: tag.uuid == '' ? null : tag.uuid,
newTagUuid: tag.uuid == '' ? null : tag.uuid,
tag: tag.tag,
productUuid: tag.product?.uuid));
}
@ -562,9 +570,7 @@ class SpaceManagementBloc
}
if (prevSubspaces != null && newSubspaces != null) {
final newSubspaceMap = {
for (var subspace in newSubspaces) subspace.uuid: subspace
};
final newSubspaceMap = {for (var subspace in newSubspaces) subspace.uuid: subspace};
for (var prevSubspace in prevSubspaces) {
final newSubspace = newSubspaceMap[prevSubspace.uuid];
@ -601,10 +607,8 @@ class SpaceManagementBloc
: [];
final createSubspaceBodyModels = space.subspaces?.map((subspace) {
final tagBodyModels = subspace.tags
?.map((tag) => tag.toCreateTagBodyModel())
.toList() ??
[];
final tagBodyModels =
subspace.tags?.map((tag) => tag.toCreateTagBodyModel()).toList() ?? [];
return CreateSubspaceModel()
..subspaceName = subspace.subspaceName
..tags = tagBodyModels;
@ -657,12 +661,11 @@ class SpaceManagementBloc
return result.toList(); // Convert back to a list
}
void _onLoadSpaceModel(
SpaceModelLoadEvent event, Emitter<SpaceManagementState> emit) async {
void _onLoadSpaceModel(SpaceModelLoadEvent event, Emitter<SpaceManagementState> emit) async {
emit(SpaceManagementLoading());
try {
var prevState = state;
await fetchTags();
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
var spaceBloc = event.context.read<SpaceTreeBloc>();
List<CommunityModel> communities = spaceBloc.state.communityList;
@ -674,8 +677,7 @@ class SpaceManagementBloc
List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async {
List<SpaceModel> spaces =
await _fetchSpacesForCommunity(community.uuid);
List<SpaceModel> spaces = await _fetchSpacesForCommunity(community.uuid);
return CommunityModel(
uuid: community.uuid,
createdAt: community.createdAt,
@ -692,10 +694,10 @@ class SpaceManagementBloc
}
emit(SpaceModelLoaded(
communities: communities,
products: _cachedProducts ?? [],
spaceModels: prevSpaceModels,
));
communities: communities,
products: _cachedProducts ?? [],
spaceModels: prevSpaceModels,
allTags: _cachedTags ?? []));
} catch (e) {
emit(SpaceManagementError('Error loading communities and spaces: $e'));
}
@ -713,7 +715,7 @@ class SpaceManagementBloc
tagUpdates.add(TagModelUpdate(
action: custom_action.Action.add,
tag: newTag.tag,
uuid: newTag.uuid,
newTagUuid: newTag.uuid,
productUuid: newTag.product?.uuid,
));
}
@ -724,17 +726,14 @@ class SpaceManagementBloc
// Case 1: Tags deleted
if (prevTags != null && newTags != null) {
for (var prevTag in prevTags) {
final existsInNew =
newTags.any((newTag) => newTag.uuid == prevTag.uuid);
final existsInNew = newTags.any((newTag) => newTag.uuid == prevTag.uuid);
if (!existsInNew) {
tagUpdates.add(TagModelUpdate(
action: custom_action.Action.delete, uuid: prevTag.uuid));
tagUpdates.add(TagModelUpdate(action: custom_action.Action.delete, uuid: prevTag.uuid));
}
}
} else if (prevTags != null && newTags == null) {
for (var prevTag in prevTags) {
tagUpdates.add(TagModelUpdate(
action: custom_action.Action.delete, uuid: prevTag.uuid));
tagUpdates.add(TagModelUpdate(action: custom_action.Action.delete, uuid: prevTag.uuid));
}
}
@ -749,7 +748,7 @@ class SpaceManagementBloc
tagUpdates.add(TagModelUpdate(
action: custom_action.Action.add,
tag: newTag.tag,
uuid: newTag.uuid == '' ? null : newTag.uuid,
newTagUuid: newTag.uuid == '' ? null : newTag.uuid,
productUuid: newTag.product?.uuid));
processedTags.add(newTag.tag);
}
@ -766,6 +765,7 @@ class SpaceManagementBloc
tagUpdates.add(TagModelUpdate(
action: custom_action.Action.update,
uuid: newTag.uuid,
newTagUuid: newTag.uuid,
tag: newTag.tag,
));
} else {}

View File

@ -2,6 +2,7 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_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/space_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';
abstract class SpaceManagementState extends Equatable {
@ -21,22 +22,25 @@ class SpaceManagementLoaded extends SpaceManagementState {
CommunityModel? selectedCommunity;
SpaceModel? selectedSpace;
List<SpaceTemplateModel>? spaceModels;
List<Tag> allTags;
SpaceManagementLoaded(
{required this.communities,
required this.products,
this.selectedCommunity,
this.selectedSpace,
this.spaceModels});
this.spaceModels,
required this.allTags});
}
class BlankState extends SpaceManagementState {
final List<CommunityModel> communities;
final List<ProductModel> products;
List<SpaceTemplateModel>? spaceModels;
final List<Tag> allTags;
BlankState(
{required this.communities, required this.products, this.spaceModels});
{required this.communities, required this.products, this.spaceModels, required this.allTags});
}
class SpaceCreationSuccess extends SpaceManagementState {
@ -61,14 +65,14 @@ class SpaceModelLoaded extends SpaceManagementState {
List<SpaceTemplateModel> spaceModels;
final List<ProductModel> products;
final List<CommunityModel> communities;
final List<Tag> allTags;
SpaceModelLoaded({
required this.communities,
required this.products,
required this.spaceModels,
});
SpaceModelLoaded(
{required this.communities,
required this.products,
required this.spaceModels,
required this.allTags});
@override
List<Object> get props => [communities, products, spaceModels];
List<Object> get props => [communities, products, spaceModels, allTags];
}

View File

@ -25,22 +25,21 @@ class Tag extends BaseTag {
return Tag(
uuid: json['uuid'] ?? '',
internalId: internalId,
tag: json['tag'] ?? '',
product: json['product'] != null
? ProductModel.fromMap(json['product'])
: null,
tag: json['name'] ?? '',
product: json['product'] != null ? ProductModel.fromMap(json['product']) : null,
);
}
@override
Tag copyWith({
String? uuid,
String? tag,
ProductModel? product,
String? location,
String? internalId,
}) {
return Tag(
uuid: uuid,
uuid: uuid ?? this.uuid,
tag: tag ?? this.tag,
product: product ?? this.product,
location: location ?? this.location,
@ -60,7 +59,7 @@ class Tag extends BaseTag {
extension TagModelExtensions on Tag {
TagBodyModel toTagBodyModel() {
return TagBodyModel()
..uuid = uuid ?? ''
..uuid = uuid
..tag = tag ?? ''
..productUuid = product?.uuid;
}

View File

@ -1,5 +1,3 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
@ -62,9 +60,9 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
selectedSpace: null,
products: state.products,
shouldNavigateToSpaceModelPage: false,
projectTags: state.allTags,
);
} else if (state is SpaceManagementLoaded) {
return LoadedSpaceView(
communities: state.communities,
selectedCommunity: state.selectedCommunity,
@ -72,6 +70,7 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
products: state.products,
spaceModels: state.spaceModels,
shouldNavigateToSpaceModelPage: false,
projectTags: state.allTags,
);
} else if (state is SpaceModelLoaded) {
return LoadedSpaceView(
@ -79,6 +78,7 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
products: state.products,
spaceModels: state.spaceModels,
shouldNavigateToSpaceModelPage: true,
projectTags: state.allTags,
);
} else if (state is SpaceManagementError) {
return Center(child: Text('Error: ${state.errorMessage}'));

View File

@ -36,16 +36,17 @@ class CommunityStructureArea extends StatefulWidget {
final List<CommunityModel> communities;
final List<SpaceModel> spaces;
final List<SpaceTemplateModel>? spaceModels;
final List<Tag> projectTags;
CommunityStructureArea({
this.selectedCommunity,
this.selectedSpace,
required this.communities,
this.products,
required this.spaces,
this.onSpaceSelected,
this.spaceModels,
});
CommunityStructureArea(
{this.selectedCommunity,
this.selectedSpace,
required this.communities,
this.products,
required this.spaces,
this.onSpaceSelected,
this.spaceModels,
required this.projectTags});
@override
_CommunityStructureAreaState createState() => _CommunityStructureAreaState();
@ -64,8 +65,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void initState() {
super.initState();
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections =
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces();
_nameController = TextEditingController(
text: widget.selectedCommunity?.name ?? '',
@ -92,14 +92,12 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
if (oldWidget.spaces != widget.spaces) {
setState(() {
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections =
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces();
});
}
if (widget.selectedSpace != oldWidget.selectedSpace &&
widget.selectedSpace != null) {
if (widget.selectedSpace != oldWidget.selectedSpace && widget.selectedSpace != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_moveToSpace(widget.selectedSpace!);
});
@ -182,8 +180,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
connection, widget.selectedSpace)
? 1.0
: 0.3, // Adjust opacity
child: CustomPaint(
painter: CurvedLinePainter([connection])),
child: CustomPaint(painter: CurvedLinePainter([connection])),
),
for (var entry in spaces.asMap().entries)
if (entry.value.status != SpaceStatus.deleted &&
@ -193,15 +190,12 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
top: entry.value.position.dy,
child: SpaceCardWidget(
index: entry.key,
onButtonTap: (int index, Offset newPosition,
String direction) {
_showCreateSpaceDialog(
screenSize,
position:
spaces[index].position + newPosition,
parentIndex: index,
direction: direction,
);
onButtonTap: (int index, Offset newPosition, String direction) {
_showCreateSpaceDialog(screenSize,
position: spaces[index].position + newPosition,
parentIndex: index,
direction: direction,
projectTags: widget.projectTags);
},
position: entry.value.position,
isHovered: entry.value.isHovered,
@ -211,9 +205,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
_updateNodePosition(entry.value, newPosition);
},
buildSpaceContainer: (int index) {
final bool isHighlighted =
SpaceHelper.isHighlightedSpace(
spaces[index], widget.selectedSpace);
final bool isHighlighted = SpaceHelper.isHighlightedSpace(
spaces[index], widget.selectedSpace);
return Opacity(
opacity: isHighlighted ? 1.0 : 0.3,
@ -238,7 +231,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
onTap: () {
_showCreateSpaceDialog(screenSize,
canvasHeight: canvasHeight,
canvasWidth: canvasWidth);
canvasWidth: canvasWidth,
projectTags: widget.projectTags);
},
),
),
@ -292,26 +286,22 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
int? parentIndex,
String? direction,
double? canvasWidth,
double? canvasHeight}) {
double? canvasHeight,
required List<Tag> projectTags}) {
showDialog(
context: context,
builder: (BuildContext context) {
return CreateSpaceDialog(
products: widget.products,
spaceModels: widget.spaceModels,
allTags:
TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
parentSpace: parentIndex != null ? spaces[parentIndex] : null,
onCreateSpace: (String name,
String icon,
List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel,
List<SubspaceModel>? subspaces,
List<Tag>? tags) {
projectTags: projectTags,
onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) {
setState(() {
// Set the first space in the center or use passed position
Offset centerPosition =
position ?? ConnectionHelper.getCenterPosition(screenSize);
Offset centerPosition = position ?? ConnectionHelper.getCenterPosition(screenSize);
SpaceModel newSpace = SpaceModel(
name: name,
icon: icon,
@ -356,21 +346,17 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
spaceModels: widget.spaceModels,
name: widget.selectedSpace!.name,
icon: widget.selectedSpace!.icon,
parentSpace: SpaceHelper.findSpaceByInternalId(
widget.selectedSpace?.parent?.internalId, spaces),
projectTags: widget.projectTags,
parentSpace:
SpaceHelper.findSpaceByInternalId(widget.selectedSpace?.parent?.internalId, spaces),
editSpace: widget.selectedSpace,
currentSpaceModel: widget.selectedSpace?.spaceModel,
tags: widget.selectedSpace?.tags,
subspaces: widget.selectedSpace?.subspaces,
isEdit: true,
allTags: TagHelper.getAllTagValues(
widget.communities, widget.spaceModels),
onCreateSpace: (String name,
String icon,
List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel,
List<SubspaceModel>? subspaces,
List<Tag>? tags) {
allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) {
setState(() {
// Update the space's properties
widget.selectedSpace!.name = name;
@ -380,8 +366,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
widget.selectedSpace!.tags = tags;
if (widget.selectedSpace!.status != SpaceStatus.newSpace) {
widget.selectedSpace!.status =
SpaceStatus.modified; // Mark as modified
widget.selectedSpace!.status = SpaceStatus.modified; // Mark as modified
}
for (var space in spaces) {
@ -411,8 +396,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
List<SpaceModel> result = [];
void flatten(SpaceModel space) {
if (space.status == SpaceStatus.deleted ||
space.status == SpaceStatus.parentDeleted) {
if (space.status == SpaceStatus.deleted || space.status == SpaceStatus.parentDeleted) {
return;
}
result.add(space);
@ -527,16 +511,13 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void _selectSpace(BuildContext context, SpaceModel space) {
context.read<SpaceManagementBloc>().add(
SelectSpaceEvent(
selectedCommunity: widget.selectedCommunity,
selectedSpace: space),
SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: space),
);
}
void _deselectSpace(BuildContext context) {
context.read<SpaceManagementBloc>().add(
SelectSpaceEvent(
selectedCommunity: widget.selectedCommunity, selectedSpace: null),
SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: null),
);
}
@ -625,19 +606,16 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
const double horizontalGap = 200.0;
const double verticalGap = 100.0;
SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition,
SpaceModel? duplicatedParent) {
Offset newPosition =
Offset(parentPosition.dx + horizontalGap, original.position.dy);
SpaceModel duplicateRecursive(
SpaceModel original, Offset parentPosition, SpaceModel? duplicatedParent) {
Offset newPosition = Offset(parentPosition.dx + horizontalGap, original.position.dy);
while (spaces.any((s) =>
(s.position - newPosition).distance < horizontalGap &&
s.status != SpaceStatus.deleted)) {
(s.position - newPosition).distance < horizontalGap && s.status != SpaceStatus.deleted)) {
newPosition += Offset(horizontalGap, 0);
}
final duplicatedName =
SpaceHelper.generateUniqueSpaceName(original.name, spaces);
final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces);
final List<SubspaceModel>? duplicatedSubspaces;
final List<Tag>? duplicatedTags;
@ -681,8 +659,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
if (original.parent != null && duplicatedParent == null) {
final originalParent = original.parent!;
final duplicatedParent =
originalToDuplicate[originalParent] ?? originalParent;
final duplicatedParent = originalToDuplicate[originalParent] ?? originalParent;
final parentConnection = Connection(
startSpace: duplicatedParent,
@ -698,8 +675,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
final childrenWithDownDirection = original.children
.where((child) =>
child.incomingConnection?.direction == "down" &&
child.status != SpaceStatus.deleted)
child.incomingConnection?.direction == "down" && child.status != SpaceStatus.deleted)
.toList();
Offset childStartPosition = childrenWithDownDirection.length == 1
@ -707,8 +683,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
: newPosition + Offset(0, verticalGap);
for (final child in original.children) {
final isDownDirection =
child.incomingConnection?.direction == "down" ?? false;
final isDownDirection = child.incomingConnection?.direction == "down" ?? false;
if (isDownDirection && childrenWithDownDirection.length == 1) {
childStartPosition = duplicated.position + Offset(0, verticalGap);
@ -716,8 +691,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
childStartPosition = duplicated.position + Offset(horizontalGap, 0);
}
final duplicatedChild =
duplicateRecursive(child, childStartPosition, duplicated);
final duplicatedChild = duplicateRecursive(child, childStartPosition, duplicated);
duplicated.children.add(duplicatedChild);
childStartPosition += Offset(0, verticalGap);
}
@ -728,8 +702,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
if (space.parent == null) {
duplicateRecursive(space, space.position, null);
} else {
final duplicatedParent =
originalToDuplicate[space.parent!] ?? space.parent!;
final duplicatedParent = originalToDuplicate[space.parent!] ?? space.parent!;
duplicateRecursive(space, space.position, duplicatedParent);
}
}

View File

@ -42,6 +42,7 @@ class CreateSpaceDialog extends StatefulWidget {
final List<Tag>? tags;
final List<String>? allTags;
final SpaceTemplateModel? currentSpaceModel;
final List<Tag> projectTags;
const CreateSpaceDialog(
{super.key,
@ -57,7 +58,8 @@ class CreateSpaceDialog extends StatefulWidget {
this.spaceModels,
this.subspaces,
this.tags,
this.currentSpaceModel});
this.currentSpaceModel,
required this.projectTags});
@override
CreateSpaceDialogState createState() => CreateSpaceDialogState();
@ -80,10 +82,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
super.initState();
selectedIcon = widget.icon ?? Assets.location;
nameController = TextEditingController(text: widget.name ?? '');
selectedProducts =
widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
isOkButtonEnabled =
enteredName.isNotEmpty || nameController.text.isNotEmpty;
selectedProducts = widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
isOkButtonEnabled = enteredName.isNotEmpty || nameController.text.isNotEmpty;
if (widget.currentSpaceModel != null) {
subspaces = [];
tags = [];
@ -96,15 +96,13 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
@override
Widget build(BuildContext context) {
bool isSpaceModelDisabled = (tags != null && tags!.isNotEmpty ||
subspaces != null && subspaces!.isNotEmpty);
bool isSpaceModelDisabled =
(tags != null && tags!.isNotEmpty || subspaces != null && subspaces!.isNotEmpty);
bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null);
final screenWidth = MediaQuery.of(context).size.width;
return AlertDialog(
title: widget.isEdit
? const Text('Edit Space')
: const Text('Create New Space'),
title: widget.isEdit ? const Text('Edit Space') : const Text('Create New Space'),
backgroundColor: ColorsManager.whiteColors,
content: SizedBox(
width: screenWidth * 0.5,
@ -178,7 +176,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
isNameFieldInvalid = value.isEmpty;
if (!isNameFieldInvalid) {
if (SpaceHelper.isNameConflict(value, widget.parentSpace, widget.editSpace)) {
if (SpaceHelper.isNameConflict(
value, widget.parentSpace, widget.editSpace)) {
isNameFieldExist = true;
isOkButtonEnabled = false;
} else {
@ -245,9 +244,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
padding: EdgeInsets.zero,
),
onPressed: () {
isSpaceModelDisabled
? null
: _showLinkSpaceModelDialog(context);
isSpaceModelDisabled ? null : _showLinkSpaceModelDialog(context);
},
child: ButtonContentWidget(
svgAssets: Assets.link,
@ -257,8 +254,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
)
: Container(
width: screenWidth * 0.25,
padding: const EdgeInsets.symmetric(
vertical: 10.0, horizontal: 16.0),
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
decoration: BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(10),
@ -273,8 +269,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: ColorsManager.spaceColor),
.copyWith(color: ColorsManager.spaceColor),
),
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
@ -343,8 +338,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
onPressed: () async {
isTagsAndSubspaceModelDisabled
? null
: _showSubSpaceDialog(context, enteredName,
[], false, widget.products, subspaces);
: _showSubSpaceDialog(
context, enteredName, [], false, widget.products, subspaces);
},
child: ButtonContentWidget(
icon: Icons.add,
@ -371,22 +366,16 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
if (subspaces != null)
...subspaces!.map((subspace) {
return Column(
crossAxisAlignment:
CrossAxisAlignment.start,
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();
bool nameExists = subspaces!.any((s) {
bool isSameId = s.internalId == subspace.internalId;
bool isSameName =
s.subspaceName.trim().toLowerCase() ==
updatedName.trim().toLowerCase();
return !isSameId && isSameName;
});
@ -395,8 +384,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
},
onNameChanged: (updatedName) {
setState(() {
subspace.subspaceName =
updatedName;
subspace.subspaceName = updatedName;
});
},
),
@ -405,8 +393,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
}),
EditChip(
onTap: () async {
_showSubSpaceDialog(context, enteredName,
[], true, widget.products, subspaces);
_showSubSpaceDialog(context, enteredName, [], true,
widget.products, subspaces);
},
)
],
@ -415,9 +403,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
),
const SizedBox(height: 10),
(tags?.isNotEmpty == true ||
subspaces?.any((subspace) =>
subspace.tags?.isNotEmpty == true) ==
true)
subspaces?.any((subspace) => subspace.tags?.isNotEmpty == true) == true)
? SizedBox(
width: screenWidth * 0.25,
child: Container(
@ -437,16 +423,14 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
// Combine tags from spaceModel and subspaces
...TagHelper.groupTags([
...?tags,
...?subspaces?.expand(
(subspace) => subspace.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',
entry.key.icon ?? 'assets/icons/gateway.svg',
fit: BoxFit.contain,
),
),
@ -455,15 +439,11 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(
color: ColorsManager
.spaceColor),
?.copyWith(color: ColorsManager.spaceColor),
),
backgroundColor:
ColorsManager.whiteColors,
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(16),
borderRadius: BorderRadius.circular(16),
side: const BorderSide(
color: ColorsManager.spaceColor,
),
@ -472,23 +452,21 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
),
EditChip(onTap: () async {
final result = await showDialog(
await showDialog(
context: context,
builder: (context) => AssignTagDialog(
products: widget.products,
subspaces: subspaces,
allTags: widget.allTags,
addedProducts: TagHelper
.createInitialSelectedProductsForTags(
addedProducts:
TagHelper.createInitialSelectedProductsForTags(
tags ?? [], subspaces),
title: 'Edit Device',
initialTags:
TagHelper.generateInitialForTags(
spaceTags: tags,
subspaces: subspaces),
initialTags: TagHelper.generateInitialForTags(
spaceTags: tags, subspaces: subspaces),
spaceName: widget.name ?? '',
onSave:
(updatedTags, updatedSubspaces) {
projectTags: widget.projectTags,
onSave: (updatedTags, updatedSubspaces) {
setState(() {
tags = updatedTags;
subspaces = updatedSubspaces;
@ -547,25 +525,17 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
});
return;
} else {
String newName = enteredName.isNotEmpty
? enteredName
: (widget.name ?? '');
String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? '');
if (newName.isNotEmpty) {
widget.onCreateSpace(
newName,
selectedIcon,
selectedProducts,
selectedSpaceModel,
subspaces,
tags);
widget.onCreateSpace(newName, selectedIcon, selectedProducts,
selectedSpaceModel, subspaces, tags);
Navigator.of(context).pop();
}
}
},
borderRadius: 10,
backgroundColor: isOkButtonEnabled
? ColorsManager.secondaryColor
: ColorsManager.grayColor,
backgroundColor:
isOkButtonEnabled ? ColorsManager.secondaryColor : ColorsManager.grayColor,
foregroundColor: ColorsManager.whiteColors,
child: const Text('OK'),
),
@ -592,7 +562,6 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
);
}
void _showLinkSpaceModelDialog(BuildContext context) {
showDialog(
context: context,
@ -613,13 +582,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
);
}
void _showSubSpaceDialog(
BuildContext context,
String name,
final List<Tag>? spaceTags,
bool isEdit,
List<ProductModel>? products,
final List<SubspaceModel>? existingSubSpaces) {
void _showSubSpaceDialog(BuildContext context, String name, final List<Tag>? spaceTags,
bool isEdit, List<ProductModel>? products, final List<SubspaceModel>? existingSubSpaces) {
showDialog(
context: context,
builder: (BuildContext context) {
@ -634,12 +598,10 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
final List<Tag> tagsToAppendToSpace = [];
if (slectedSubspaces != null) {
final updatedIds =
slectedSubspaces.map((s) => s.internalId).toSet();
final updatedIds = slectedSubspaces.map((s) => s.internalId).toSet();
if (existingSubSpaces != null) {
final deletedSubspaces = existingSubSpaces
.where((s) => !updatedIds.contains(s.internalId))
.toList();
final deletedSubspaces =
existingSubSpaces.where((s) => !updatedIds.contains(s.internalId)).toList();
for (var s in deletedSubspaces) {
if (s.tags != null) {
tagsToAppendToSpace.addAll(s.tags!);
@ -659,20 +621,20 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
);
}
void _showTagCreateDialog(BuildContext context, String name, bool isEdit,
List<ProductModel>? products) {
void _showTagCreateDialog(
BuildContext context, String name, bool isEdit, List<ProductModel>? products) {
isEdit
? showDialog(
context: context,
builder: (BuildContext context) {
return AssignTagDialog(
title: 'Edit Device',
addedProducts: TagHelper.createInitialSelectedProductsForTags(
tags, subspaces),
addedProducts: TagHelper.createInitialSelectedProductsForTags(tags, subspaces),
spaceName: name,
products: products,
subspaces: subspaces,
allTags: widget.allTags,
projectTags: widget.projectTags,
onSave: (selectedSpaceTags, selectedSubspaces) {
setState(() {
tags = selectedSpaceTags;
@ -682,8 +644,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
if (subspaces != null) {
for (final subspace in subspaces!) {
for (final selectedSubspace in selectedSubspaces) {
if (subspace.subspaceName ==
selectedSubspace.subspaceName) {
if (subspace.subspaceName == selectedSubspace.subspaceName) {
subspace.tags = selectedSubspace.tags;
}
}
@ -705,9 +666,9 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
spaceTags: tags,
isCreate: true,
allTags: widget.allTags,
projectTags: widget.projectTags,
initialSelectedProducts:
TagHelper.createInitialSelectedProductsForTags(
tags, subspaces),
TagHelper.createInitialSelectedProductsForTags(tags, subspaces),
onSave: (selectedSpaceTags, selectedSubspaces) {
setState(() {
tags = selectedSpaceTags;
@ -717,8 +678,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
if (subspaces != null) {
for (final subspace in subspaces!) {
for (final selectedSubspace in selectedSubspaces) {
if (subspace.subspaceName ==
selectedSubspace.subspaceName) {
if (subspace.subspaceName == selectedSubspace.subspaceName) {
subspace.tags = selectedSubspace.tags;
}
}

View File

@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_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/space_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/gradient_canvas_border_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart';
@ -20,16 +21,17 @@ class LoadedSpaceView extends StatefulWidget {
final List<ProductModel>? products;
final List<SpaceTemplateModel>? spaceModels;
final bool shouldNavigateToSpaceModelPage;
final List<Tag> projectTags;
const LoadedSpaceView({
super.key,
required this.communities,
this.selectedCommunity,
this.selectedSpace,
this.products,
this.spaceModels,
required this.shouldNavigateToSpaceModelPage,
});
const LoadedSpaceView(
{super.key,
required this.communities,
this.selectedCommunity,
this.selectedSpace,
this.products,
this.spaceModels,
required this.shouldNavigateToSpaceModelPage,
required this.projectTags});
@override
_LoadedSpaceViewState createState() => _LoadedSpaceViewState();
@ -93,6 +95,7 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
child: SpaceModelPage(
products: widget.products,
onSpaceModelsUpdated: _onSpaceModelsUpdated,
projectTags: widget.projectTags,
),
),
),
@ -113,6 +116,7 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
products: widget.products,
communities: widget.communities,
spaceModels: _spaceModels,
projectTags: widget.projectTags,
),
],
),

View File

@ -4,17 +4,16 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_e
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart';
class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final List<String> allTags;
final List<Tag> projectTags;
AssignTagBloc(this.allTags) : super(AssignTagInitial()) {
AssignTagBloc(this.projectTags) : super(AssignTagInitial()) {
on<InitializeTags>((event, emit) {
final initialTags = event.initialTags ?? [];
final existingTagCounts = <String, int>{};
for (var tag in initialTags) {
if (tag.product != null) {
existingTagCounts[tag.product!.uuid] =
(existingTagCounts[tag.product!.uuid] ?? 0) + 1;
existingTagCounts[tag.product!.uuid] = (existingTagCounts[tag.product!.uuid] ?? 0) + 1;
}
}
@ -23,17 +22,14 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
for (var selectedProduct in event.addedProducts) {
final existingCount = existingTagCounts[selectedProduct.productId] ?? 0;
if (selectedProduct.count == 0 ||
selectedProduct.count <= existingCount) {
tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
if (selectedProduct.count == 0 || selectedProduct.count <= existingCount) {
tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
continue;
}
final missingCount = selectedProduct.count - existingCount;
tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
if (missingCount > 0) {
tags.addAll(List.generate(
@ -47,11 +43,11 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
}
}
final updatedTags = _calculateAvailableTags(allTags, tags);
final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagLoaded(
tags: tags,
updatedTags: updatedTags,
updatedTags: updatedTags,
isSaveEnabled: _validateTags(tags),
errorMessage: '',
));
@ -62,9 +58,16 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags);
tags[event.index] = tags[event.index].copyWith(tag: event.tag);
if (event.index < 0 || event.index >= tags.length) return;
final updatedTags = _calculateAvailableTags(allTags, tags);
tags[event.index] = tags[event.index].copyWith(
tag: event.tag.tag,
uuid: event.tag.uuid,
product: event.tag.product,
internalId: event.tag.internalId,
location: event.tag.location,
);
final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagLoaded(
tags: tags,
@ -82,10 +85,9 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final tags = List<Tag>.from(currentState.tags);
// Update the location
tags[event.index] =
tags[event.index].copyWith(location: event.location);
tags[event.index] = tags[event.index].copyWith(location: event.location);
final updatedTags = _calculateAvailableTags(allTags, tags);
final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagLoaded(
tags: tags,
@ -104,7 +106,7 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
emit(AssignTagLoaded(
tags: tags,
updatedTags: _calculateAvailableTags(allTags, tags),
updatedTags: _calculateAvailableTags(projectTags, tags),
isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
));
@ -115,11 +117,10 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final currentState = state;
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags)
..remove(event.tagToDelete);
final tags = List<Tag>.from(currentState.tags)..remove(event.tagToDelete);
// Recalculate available tags
final updatedTags = _calculateAvailableTags(allTags, tags);
final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagLoaded(
tags: tags,
@ -140,10 +141,8 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
// Get validation error for duplicate tags
String? _getValidationError(List<Tag> tags) {
final nonEmptyTags = tags
.map((tag) => tag.tag?.trim() ?? '')
.where((tag) => tag.isNotEmpty)
.toList();
final nonEmptyTags =
tags.map((tag) => tag.tag?.trim() ?? '').where((tag) => tag.isNotEmpty).toList();
final duplicateTags = nonEmptyTags
.fold<Map<String, int>>({}, (map, tag) {
@ -162,14 +161,16 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
return null;
}
List<String> _calculateAvailableTags(List<String> allTags, List<Tag> tags) {
final selectedTags = tags
List<Tag> _calculateAvailableTags(List<Tag> allTags, List<Tag> selectedTags) {
final selectedTagSet = selectedTags
.where((tag) => (tag.tag?.trim().isNotEmpty ?? false))
.map((tag) => tag.tag!.trim())
.toSet();
final availableTags =
allTags.where((tag) => !selectedTags.contains(tag.trim())).toList();
final availableTags = allTags
.where((tag) => tag.tag != null && !selectedTagSet.contains(tag.tag!.trim()))
.toList();
return availableTags;
}
}

View File

@ -24,7 +24,7 @@ class InitializeTags extends AssignTagEvent {
class UpdateTagEvent extends AssignTagEvent {
final int index;
final String tag;
final Tag tag;
const UpdateTagEvent({required this.index, required this.tag});

View File

@ -5,7 +5,7 @@ abstract class AssignTagState extends Equatable {
const AssignTagState();
@override
List<Object> get props => [];
List<Object?> get props => [];
}
class AssignTagInitial extends AssignTagState {}
@ -14,7 +14,7 @@ class AssignTagLoading extends AssignTagState {}
class AssignTagLoaded extends AssignTagState {
final List<Tag> tags;
final List<String> updatedTags;
final List<Tag> updatedTags;
final bool isSaveEnabled;
final String? errorMessage;
@ -27,8 +27,7 @@ class AssignTagLoaded extends AssignTagState {
});
@override
List<Object> get props =>
[tags, updatedTags, isSaveEnabled, errorMessage ?? ''];
List<Object?> get props => [tags, updatedTags, isSaveEnabled, errorMessage ?? ''];
}
class AssignTagError extends AssignTagState {
@ -37,5 +36,5 @@ class AssignTagError extends AssignTagState {
const AssignTagError(this.errorMessage);
@override
List<Object> get props => [errorMessage];
List<Object?> get props => [errorMessage];
}

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/dialog_dropdown.dart';
import 'package:syncrow_web/common/dialog_textfield_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';
@ -26,6 +26,7 @@ class AssignTagDialog extends StatelessWidget {
final String spaceName;
final String title;
final Function(List<Tag>, List<SubspaceModel>?)? onSave;
final List<Tag> projectTags;
const AssignTagDialog(
{Key? key,
@ -37,18 +38,17 @@ class AssignTagDialog extends StatelessWidget {
this.allTags,
required this.spaceName,
required this.title,
this.onSave})
this.onSave,
required this.projectTags})
: super(key: key);
@override
Widget build(BuildContext context) {
final List<String> locations = (subspaces ?? [])
.map((subspace) => subspace.subspaceName)
.toList()
..add('Main Space');
final List<String> locations =
(subspaces ?? []).map((subspace) => subspace.subspaceName).toList()..add('Main Space');
return BlocProvider(
create: (_) => AssignTagBloc(allTags ?? [])
create: (_) => AssignTagBloc(projectTags)
..add(InitializeTags(
initialTags: initialTags,
addedProducts: addedProducts,
@ -70,8 +70,7 @@ class AssignTagDialog extends StatelessWidget {
ClipRRect(
borderRadius: BorderRadius.circular(20),
child: DataTable(
headingRowColor: WidgetStateProperty.all(
ColorsManager.dataHeaderGrey),
headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey),
key: ValueKey(state.tags.length),
border: TableBorder.all(
color: ColorsManager.dataHeaderGrey,
@ -80,22 +79,15 @@ class AssignTagDialog extends StatelessWidget {
),
columns: [
DataColumn(
label: Text('#',
style:
Theme.of(context).textTheme.bodyMedium)),
label: Text('#', style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label: Text('Device',
style:
Theme.of(context).textTheme.bodyMedium)),
label: Text('Device', style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
numeric: false,
label: Text('Tag',
style:
Theme.of(context).textTheme.bodyMedium)),
label: Text('Tag', style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label: Text('Location',
style:
Theme.of(context).textTheme.bodyMedium)),
label:
Text('Location', style: Theme.of(context).textTheme.bodyMedium)),
],
rows: state.tags.isEmpty
? [
@ -103,12 +95,8 @@ class AssignTagDialog extends StatelessWidget {
DataCell(
Center(
child: Text('No Data Available',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
color: ColorsManager
.lightGrayColor,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: ColorsManager.lightGrayColor,
)),
),
),
@ -126,8 +114,7 @@ class AssignTagDialog extends StatelessWidget {
DataCell(Text((index + 1).toString())),
DataCell(
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
@ -141,31 +128,25 @@ class AssignTagDialog extends StatelessWidget {
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager
.lightGrayColor,
color: ColorsManager.lightGrayColor,
width: 1.0,
),
),
child: IconButton(
icon: const Icon(
Icons.close,
color: ColorsManager
.lightGreyColor,
color: ColorsManager.lightGreyColor,
size: 16,
),
onPressed: () {
context
.read<AssignTagBloc>()
.add(DeleteTag(
tagToDelete: tag,
tags: state.tags));
context.read<AssignTagBloc>().add(
DeleteTag(tagToDelete: tag, tags: state.tags));
controllers.removeAt(index);
},
tooltip: 'Delete Tag',
padding: EdgeInsets.zero,
constraints:
const BoxConstraints(),
constraints: const BoxConstraints(),
),
),
],
@ -173,23 +154,20 @@ class AssignTagDialog extends StatelessWidget {
),
DataCell(
Container(
alignment: Alignment
.centerLeft, // Align cell content to the left
alignment:
Alignment.centerLeft, // Align cell content to the left
child: SizedBox(
width: double
.infinity, // Ensure full width for dropdown
child: DialogTextfieldDropdown(
key: ValueKey(
'dropdown_${Uuid().v4()}_${index}'),
width: double.infinity,
child: TagDialogTextfieldDropdown(
key: ValueKey('dropdown_${const Uuid().v4()}_$index'),
items: state.updatedTags,
initialValue: tag.tag,
product: tag.product?.uuid ?? 'Unknown',
initialValue: tag,
onSelected: (value) {
controller.text = value;
context
.read<AssignTagBloc>()
.add(UpdateTagEvent(
controller.text = value.tag ?? '';
context.read<AssignTagBloc>().add(UpdateTagEvent(
index: index,
tag: value.trim(),
tag: value,
));
},
),
@ -201,12 +179,9 @@ class AssignTagDialog extends StatelessWidget {
width: double.infinity,
child: DialogDropdown(
items: locations,
selectedValue:
tag.location ?? 'Main Space',
selectedValue: tag.location ?? 'Main Space',
onSelected: (value) {
context
.read<AssignTagBloc>()
.add(UpdateLocation(
context.read<AssignTagBloc>().add(UpdateLocation(
index: index,
location: value,
));
@ -238,13 +213,11 @@ class AssignTagDialog extends StatelessWidget {
label: 'Add New Device',
onPressed: () async {
final updatedTags = List<Tag>.from(state.tags);
final result =
TagHelper.processTags(updatedTags, subspaces);
final result = TagHelper.processTags(updatedTags, subspaces);
final processedTags =
result['updatedTags'] as List<Tag>;
final processedSubspaces = List<SubspaceModel>.from(
result['subspaces'] as List<dynamic>);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces =
List<SubspaceModel>.from(result['subspaces'] as List<dynamic>);
Navigator.of(context).pop();
@ -253,8 +226,9 @@ class AssignTagDialog extends StatelessWidget {
builder: (context) => AddDeviceTypeWidget(
products: products,
subspaces: processedSubspaces,
initialSelectedProducts: TagHelper
.createInitialSelectedProductsForTags(
projectTags: projectTags,
initialSelectedProducts:
TagHelper.createInitialSelectedProductsForTags(
processedTags, processedSubspaces),
spaceName: spaceName,
spaceTags: processedTags,
@ -278,14 +252,11 @@ class AssignTagDialog extends StatelessWidget {
onPressed: state.isSaveEnabled
? () async {
final updatedTags = List<Tag>.from(state.tags);
final result = TagHelper.processTags(
updatedTags, subspaces);
final result = TagHelper.processTags(updatedTags, subspaces);
final processedTags =
result['updatedTags'] as List<Tag>;
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces =
List<SubspaceModel>.from(
result['subspaces'] as List<dynamic>);
List<SubspaceModel>.from(result['subspaces'] as List<dynamic>);
onSave?.call(processedTags, processedSubspaces);
Navigator.of(context).pop();
}

View File

@ -1,45 +1,40 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.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/tag_model.dart';
class AssignTagModelBloc
extends Bloc<AssignTagModelEvent, AssignTagModelState> {
final List<String> allTags;
class AssignTagModelBloc extends Bloc<AssignTagModelEvent, AssignTagModelState> {
final List<Tag> projectTags;
AssignTagModelBloc(this.allTags) : super(AssignTagModelInitial()) {
AssignTagModelBloc(this.projectTags) : super(AssignTagModelInitial()) {
on<InitializeTagModels>((event, emit) {
final initialTags = event.initialTags ?? [];
final initialTags = event.initialTags;
final existingTagCounts = <String, int>{};
for (var tag in initialTags) {
if (tag.product != null) {
existingTagCounts[tag.product!.uuid] =
(existingTagCounts[tag.product!.uuid] ?? 0) + 1;
existingTagCounts[tag.product!.uuid] = (existingTagCounts[tag.product!.uuid] ?? 0) + 1;
}
}
final tags = <TagModel>[];
final tags = <Tag>[];
for (var selectedProduct in event.addedProducts) {
final existingCount = existingTagCounts[selectedProduct.productId] ?? 0;
if (selectedProduct.count == 0 ||
selectedProduct.count <= existingCount) {
tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
if (selectedProduct.count == 0 || selectedProduct.count <= existingCount) {
tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
continue;
}
final missingCount = selectedProduct.count - existingCount;
tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
if (missingCount > 0) {
tags.addAll(List.generate(
missingCount,
(index) => TagModel(
(index) => Tag(
tag: '',
product: selectedProduct.product,
location: 'Main Space',
@ -48,7 +43,7 @@ class AssignTagModelBloc
}
}
final updatedTags = _calculateAvailableTags(allTags, tags);
final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagModelLoaded(
tags: tags,
@ -59,11 +54,20 @@ class AssignTagModelBloc
on<UpdateTag>((event, emit) {
final currentState = state;
if (currentState is AssignTagModelLoaded &&
currentState.tags.isNotEmpty) {
final tags = List<TagModel>.from(currentState.tags);
tags[event.index] = tags[event.index].copyWith(tag: event.tag);
final updatedTags = _calculateAvailableTags(allTags, tags);
if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags);
if (event.index < 0 || event.index >= tags.length) return;
tags[event.index] = tags[event.index].copyWith(
tag: event.tag.tag,
uuid: event.tag.uuid,
product: event.tag.product,
internalId: event.tag.internalId,
location: event.tag.location,
);
final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagModelLoaded(
tags: tags,
@ -77,15 +81,12 @@ class AssignTagModelBloc
on<UpdateLocation>((event, emit) {
final currentState = state;
if (currentState is AssignTagModelLoaded &&
currentState.tags.isNotEmpty) {
final tags = List<TagModel>.from(currentState.tags);
if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags);
// Use copyWith for immutability
tags[event.index] =
tags[event.index].copyWith(location: event.location);
tags[event.index] = tags[event.index].copyWith(location: event.location);
final updatedTags = _calculateAvailableTags(allTags, tags);
final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagModelLoaded(
tags: tags,
@ -99,13 +100,12 @@ class AssignTagModelBloc
on<ValidateTagModels>((event, emit) {
final currentState = state;
if (currentState is AssignTagModelLoaded &&
currentState.tags.isNotEmpty) {
final tags = List<TagModel>.from(currentState.tags);
if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags);
emit(AssignTagModelLoaded(
tags: tags,
updatedTags: _calculateAvailableTags(allTags, tags),
updatedTags: _calculateAvailableTags(projectTags, tags),
isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
));
@ -115,12 +115,10 @@ class AssignTagModelBloc
on<DeleteTagModel>((event, emit) {
final currentState = state;
if (currentState is AssignTagModelLoaded &&
currentState.tags.isNotEmpty) {
final tags = List<TagModel>.from(currentState.tags)
..remove(event.tagToDelete);
if (currentState is AssignTagModelLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags)..remove(event.tagToDelete);
final updatedTags = _calculateAvailableTags(allTags, tags);
final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagModelLoaded(
tags: tags,
@ -128,24 +126,22 @@ class AssignTagModelBloc
isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
));
}
}
});
}
bool _validateTags(List<TagModel> tags) {
bool _validateTags(List<Tag> tags) {
final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet();
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
final isValid = uniqueTags.length == tags.length && !hasEmptyTag;
return isValid;
}
String? _getValidationError(List<TagModel> tags) {
String? _getValidationError(List<Tag> tags) {
// Check for duplicate tags
final nonEmptyTags = tags
.map((tag) => tag.tag?.trim() ?? '')
.where((tag) => tag.isNotEmpty)
.toList();
final nonEmptyTags =
tags.map((tag) => tag.tag?.trim() ?? '').where((tag) => tag.isNotEmpty).toList();
final duplicateTags = nonEmptyTags
.fold<Map<String, int>>({}, (map, tag) {
@ -164,15 +160,16 @@ class AssignTagModelBloc
return null;
}
List<String> _calculateAvailableTags(
List<String> allTags, List<TagModel> tags) {
final selectedTags = tags
List<Tag> _calculateAvailableTags(List<Tag> allTags, List<Tag> selectedTags) {
final selectedTagSet = selectedTags
.where((tag) => (tag.tag?.trim().isNotEmpty ?? false))
.map((tag) => tag.tag!.trim())
.toSet();
final availableTags =
allTags.where((tag) => !selectedTags.contains(tag.trim())).toList();
final availableTags = allTags
.where((tag) => tag.tag != null && !selectedTagSet.contains(tag.tag!.trim()))
.toList();
return availableTags;
}
}

View File

@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
abstract class AssignTagModelEvent extends Equatable {
@ -10,7 +10,7 @@ abstract class AssignTagModelEvent extends Equatable {
}
class InitializeTagModels extends AssignTagModelEvent {
final List<TagModel> initialTags;
final List<Tag> initialTags;
final List<SelectedProduct> addedProducts;
const InitializeTagModels({
@ -24,7 +24,7 @@ class InitializeTagModels extends AssignTagModelEvent {
class UpdateTag extends AssignTagModelEvent {
final int index;
final String tag;
final Tag tag;
const UpdateTag({required this.index, required this.tag});
@ -45,8 +45,8 @@ class UpdateLocation extends AssignTagModelEvent {
class ValidateTagModels extends AssignTagModelEvent {}
class DeleteTagModel extends AssignTagModelEvent {
final TagModel tagToDelete;
final List<TagModel> tags;
final Tag tagToDelete;
final List<Tag> tags;
const DeleteTagModel({required this.tagToDelete, required this.tags});

View File

@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
abstract class AssignTagModelState extends Equatable {
const AssignTagModelState();
@ -13,11 +13,11 @@ class AssignTagModelInitial extends AssignTagModelState {}
class AssignTagModelLoading extends AssignTagModelState {}
class AssignTagModelLoaded extends AssignTagModelState {
final List<TagModel> tags;
final List<Tag> tags;
final bool isSaveEnabled;
final String? errorMessage;
final List<String> updatedTags;
final List<Tag> updatedTags;
const AssignTagModelLoaded({
required this.tags,

View File

@ -1,17 +1,17 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/common/dialog_dropdown.dart';
import 'package:syncrow_web/common/dialog_textfield_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/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.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/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_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';
@ -23,8 +23,8 @@ class AssignTagModelsDialog extends StatelessWidget {
final List<SubspaceTemplateModel>? subspaces;
final SpaceTemplateModel? spaceModel;
final List<TagModel> initialTags;
final ValueChanged<List<TagModel>>? onTagsAssigned;
final List<Tag> initialTags;
final ValueChanged<List<Tag>>? onTagsAssigned;
final List<SelectedProduct> addedProducts;
final List<String>? allTags;
final String spaceName;
@ -32,6 +32,7 @@ class AssignTagModelsDialog extends StatelessWidget {
final BuildContext? pageContext;
final List<String>? otherSpaceModels;
final List<SpaceTemplateModel>? allSpaceModels;
final List<Tag> projectTags;
const AssignTagModelsDialog(
{Key? key,
@ -46,18 +47,17 @@ class AssignTagModelsDialog extends StatelessWidget {
this.pageContext,
this.otherSpaceModels,
this.spaceModel,
this.allSpaceModels})
this.allSpaceModels,
required this.projectTags})
: super(key: key);
@override
Widget build(BuildContext context) {
final List<String> locations = (subspaces ?? [])
.map((subspace) => subspace.subspaceName)
.toList()
..add('Main Space');
final List<String> locations =
(subspaces ?? []).map((subspace) => subspace.subspaceName).toList()..add('Main Space');
return BlocProvider(
create: (_) => AssignTagModelBloc(allTags ?? [])
create: (_) => AssignTagModelBloc(projectTags)
..add(InitializeTagModels(
initialTags: initialTags,
addedProducts: addedProducts,
@ -81,8 +81,7 @@ class AssignTagModelsDialog extends StatelessWidget {
ClipRRect(
borderRadius: BorderRadius.circular(20),
child: DataTable(
headingRowColor: WidgetStateProperty.all(
ColorsManager.dataHeaderGrey),
headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey),
key: ValueKey(state.tags.length),
border: TableBorder.all(
color: ColorsManager.dataHeaderGrey,
@ -91,26 +90,17 @@ class AssignTagModelsDialog extends StatelessWidget {
),
columns: [
DataColumn(
label: Text('#',
style: Theme.of(context)
.textTheme
.bodyMedium)),
label: Text('#', style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label: Text('Device',
style: Theme.of(context)
.textTheme
.bodyMedium)),
style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
numeric: false,
label: Text('Tag',
style: Theme.of(context)
.textTheme
.bodyMedium)),
label:
Text('Tag', style: Theme.of(context).textTheme.bodyMedium)),
DataColumn(
label: Text('Location',
style: Theme.of(context)
.textTheme
.bodyMedium)),
style: Theme.of(context).textTheme.bodyMedium)),
],
rows: state.tags.isEmpty
? [
@ -118,13 +108,10 @@ class AssignTagModelsDialog extends StatelessWidget {
DataCell(
Center(
child: Text('No Devices Available',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
color: ColorsManager
.lightGrayColor,
)),
style:
Theme.of(context).textTheme.bodyMedium?.copyWith(
color: ColorsManager.lightGrayColor,
)),
),
),
const DataCell(SizedBox()),
@ -141,8 +128,7 @@ class AssignTagModelsDialog extends StatelessWidget {
DataCell(Text((index + 1).toString())),
DataCell(
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
@ -156,31 +142,25 @@ class AssignTagModelsDialog extends StatelessWidget {
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: ColorsManager
.lightGrayColor,
color: ColorsManager.lightGrayColor,
width: 1.0,
),
),
child: IconButton(
icon: const Icon(
Icons.close,
color: ColorsManager
.lightGreyColor,
color: ColorsManager.lightGreyColor,
size: 16,
),
onPressed: () {
context
.read<
AssignTagModelBloc>()
.add(DeleteTagModel(
tagToDelete: tag,
tags: state.tags));
context.read<AssignTagModelBloc>().add(
DeleteTagModel(
tagToDelete: tag, tags: state.tags));
controllers.removeAt(index);
},
tooltip: 'Delete Tag',
padding: EdgeInsets.zero,
constraints:
const BoxConstraints(),
constraints: const BoxConstraints(),
),
),
],
@ -191,19 +171,16 @@ class AssignTagModelsDialog extends StatelessWidget {
alignment: Alignment
.centerLeft, // Align cell content to the left
child: SizedBox(
width: double
.infinity, // Ensure full width for dropdown
child: DialogTextfieldDropdown(
key: ValueKey(
'dropdown_${Uuid().v4()}_${index}'),
width: double.infinity,
child: TagDialogTextfieldDropdown(
key: ValueKey(
'dropdown_${const Uuid().v4()}_$index'),
product: tag.product?.uuid ?? 'Unknown',
items: state.updatedTags,
initialValue: tag.tag,
initialValue: tag,
onSelected: (value) {
controller.text = value;
context
.read<
AssignTagModelBloc>()
.add(UpdateTag(
controller.text = value.tag ?? '';
context.read<AssignTagModelBloc>().add(UpdateTag(
index: index,
tag: value,
));
@ -217,12 +194,10 @@ class AssignTagModelsDialog extends StatelessWidget {
width: double.infinity,
child: DialogDropdown(
items: locations,
selectedValue: tag.location ??
'Main Space',
selectedValue: tag.location ?? 'Main Space',
onSelected: (value) {
context
.read<
AssignTagModelBloc>()
.read<AssignTagModelBloc>()
.add(UpdateLocation(
index: index,
location: value,
@ -254,17 +229,13 @@ class AssignTagModelsDialog extends StatelessWidget {
builder: (buttonContext) => CancelButton(
label: 'Add New Device',
onPressed: () async {
final updatedTags =
List<TagModel>.from(state.tags);
final updatedTags = List<Tag>.from(state.tags);
final result =
TagHelper.updateSubspaceTagModels(
updatedTags, subspaces);
TagHelper.updateSubspaceTagModels(updatedTags, subspaces);
final processedTags =
result['updatedTags'] as List<TagModel>;
final processedSubspaces =
List<SubspaceTemplateModel>.from(
result['subspaces'] as List<dynamic>);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces = List<SubspaceTemplateModel>.from(
result['subspaces'] as List<dynamic>);
if (context.mounted) {
Navigator.of(context).pop();
@ -272,28 +243,25 @@ class AssignTagModelsDialog extends StatelessWidget {
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,
spaceModel: SpaceTemplateModel(
modelName: spaceName,
tags: updatedTags,
uuid: spaceModel?.uuid,
internalId:
spaceModel?.internalId,
subspaceModels:
processedSubspaces)),
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)),
);
}
},
@ -310,22 +278,16 @@ class AssignTagModelsDialog extends StatelessWidget {
: ColorsManager.whiteColorsWithOpacity,
onPressed: state.isSaveEnabled
? () async {
final updatedTags =
List<TagModel>.from(state.tags);
final updatedTags = List<Tag>.from(state.tags);
final result =
TagHelper.updateSubspaceTagModels(
updatedTags, subspaces);
TagHelper.updateSubspaceTagModels(updatedTags, subspaces);
final processedTags =
result['updatedTags'] as List<TagModel>;
final processedSubspaces =
List<SubspaceTemplateModel>.from(
result['subspaces']
as List<dynamic>);
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces = List<SubspaceTemplateModel>.from(
result['subspaces'] as List<dynamic>);
Navigator.of(context)
.popUntil((route) => route.isFirst);
Navigator.of(context).popUntil((route) => route.isFirst);
await showDialog(
context: context,
@ -334,16 +296,15 @@ class AssignTagModelsDialog extends StatelessWidget {
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),
internalId: spaceModel?.internalId,
subspaceModels: processedSubspaces),
);
},
);

View File

@ -7,7 +7,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_mo
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/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
class TagHelper {
static Map<String, dynamic> updateTags<T>({
@ -131,9 +130,9 @@ class TagHelper {
}
static List<String> getAvailableTagModels(
List<String> allTags, List<TagModel> currentTags, TagModel currentTag) {
List<String> allTags, List<Tag> currentTags, Tag currentTag) {
List<String> availableTagsForTagModel =
TagHelper.getAvailableTags<TagModel>(
TagHelper.getAvailableTags<Tag>(
allTags: allTags,
currentTags: currentTags,
currentTag: currentTag,
@ -142,11 +141,11 @@ class TagHelper {
return availableTagsForTagModel;
}
static List<TagModel> generateInitialTags({
List<TagModel>? spaceTagModels,
static List<Tag> generateInitialTags({
List<Tag>? spaceTagModels,
List<SubspaceTemplateModel>? subspaces,
}) {
final List<TagModel> initialTags = [];
final List<Tag> initialTags = [];
if (spaceTagModels != null) {
initialTags.addAll(spaceTagModels);
@ -212,7 +211,7 @@ class TagHelper {
}
static List<SelectedProduct> createInitialSelectedProducts(
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces) {
List<Tag>? tags, List<SubspaceTemplateModel>? subspaces) {
final Map<ProductModel, int> productCounts = {};
if (tags != null) {
@ -282,7 +281,7 @@ class TagHelper {
}
static int? checkTagExistInSubspaceModels(
TagModel tag, List<dynamic>? subspaces) {
Tag tag, List<dynamic>? subspaces) {
if (subspaces == null) return null;
for (int i = 0; i < subspaces.length; i++) {
@ -298,8 +297,8 @@ class TagHelper {
}
static Map<String, dynamic> updateSubspaceTagModels(
List<TagModel> updatedTags, List<SubspaceTemplateModel>? subspaces) {
final result = TagHelper.updateTags<TagModel>(
List<Tag> updatedTags, List<SubspaceTemplateModel>? subspaces) {
final result = TagHelper.updateTags<Tag>(
updatedTags: updatedTags,
subspaces: subspaces,
getInternalId: (tag) => tag.internalId,
@ -311,7 +310,7 @@ class TagHelper {
checkTagExistInSubspace: checkTagExistInSubspaceModels,
);
final processedTags = result['updatedTags'] as List<TagModel>;
final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces =
List<SubspaceTemplateModel>.from(result['subspaces'] as List<dynamic>);

View File

@ -1,12 +0,0 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart';
class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> {
SpaceModelBloc() : super(SpaceModelInitial()) {
on<SpaceModelSelectedEvent>((event, emit) {
emit(SpaceModelSelectedState(event.selectedIndex));
});
}
}

View File

@ -1,7 +0,0 @@
abstract class SpaceModelEvent {}
class SpaceModelSelectedEvent extends SpaceModelEvent {
final int selectedIndex;
SpaceModelSelectedEvent(this.selectedIndex);
}

View File

@ -1,9 +0,0 @@
abstract class SpaceModelState {}
class SpaceModelInitial extends SpaceModelState {}
class SpaceModelSelectedState extends SpaceModelState {
final int selectedIndex;
SpaceModelSelectedState(this.selectedIndex);
}

View File

@ -0,0 +1,104 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
class LinkSpaceToModelBloc
extends Bloc<LinkSpaceToModelEvent, LinkSpaceToModelState> {
LinkSpaceToModelBloc() : super(SpaceModelInitial()) {
on<LinkSpaceModelSelectedIdsEvent>(_getSpaceIds);
on<LinkSpaceModelEvent>(_handleLinkSpaceModel);
on<ValidateSpaceModelEvent>(_validateLinkSpaceModel);
on<LinkSpaceModelSelectedEvent>((event, emit) {
emit(SpaceModelSelectedState(event.selectedIndex));
});
}
List<String> spacesListIds = [];
bool hasSelectedSpaces = false;
String validate = '';
Future<void> _getSpaceIds(LinkSpaceModelSelectedIdsEvent event,
Emitter<LinkSpaceToModelState> emit) async {
try {
BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
spacesListIds = spacesList;
}
hasSelectedSpaces =
spaceBloc.state.selectedCommunities.any((communityId) {
List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
return spacesList.isNotEmpty;
});
if (hasSelectedSpaces) {
debugPrint("At least one space is selected.");
} else {
debugPrint("No spaces selected.");
}
} catch (e) {
debugPrint("Error in _getSpaceIds: $e");
}
}
Future<void> _handleLinkSpaceModel(
LinkSpaceModelEvent event,
Emitter<LinkSpaceToModelState> emit,
) async {
emit(SpaceModelLoading());
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
await SpaceModelManagementApi().linkSpaceModel(
spaceModelUuid: event.selectedSpaceMode!,
projectId: projectUuid,
spaceUuids: spacesListIds,
isOverWrite: event.isOverWrite);
emit(SpaceModelLinkSuccess());
} on DioException catch (e) {
final errorMessage = _parseDioError(e);
emit(SpaceModelOperationFailure(errorMessage));
} catch (e) {
emit(SpaceModelOperationFailure('Unexpected error: $e'));
}
}
Future<void> _validateLinkSpaceModel(
ValidateSpaceModelEvent event,
Emitter<LinkSpaceToModelState> emit,
) async {
emit(SpaceModelLoading());
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
await SpaceModelManagementApi().validateSpaceModel(
projectUuid,
spacesListIds,
);
emit(SpaceValidationSuccess());
} on DioException catch (e) {
final errorMessage = _parseDioError(e);
if (errorMessage ==
'Selected spaces already have linked space model / sub-spaces and devices') {
emit(const AlreadyHaveLinkedState());
} else {
emit(SpaceModelOperationFailure(errorMessage));
}
} catch (e) {
emit(SpaceModelOperationFailure('Unexpected error: $e'));
}
}
String _parseDioError(DioException e) {
if (e.response?.data is Map<String, dynamic>) {
return e.response!.data['error']['message'] ?? 'Unknown error occurred';
}
return e.message ?? 'Network request failed';
}
}

View File

@ -0,0 +1,22 @@
import 'package:flutter/material.dart';
abstract class LinkSpaceToModelEvent {}
class LinkSpaceModelSelectedEvent extends LinkSpaceToModelEvent {
final int selectedIndex;
LinkSpaceModelSelectedEvent(this.selectedIndex);
}
class LinkSpaceModelSelectedIdsEvent extends LinkSpaceToModelEvent {}
class LinkSpaceModelEvent extends LinkSpaceToModelEvent {
final String? selectedSpaceMode;
final bool isOverWrite;
LinkSpaceModelEvent({this.selectedSpaceMode, this.isOverWrite = false});
}
class ValidateSpaceModelEvent extends LinkSpaceToModelEvent {
BuildContext? context;
ValidateSpaceModelEvent({this.context});
}

View File

@ -0,0 +1,35 @@
abstract class LinkSpaceToModelState {
const LinkSpaceToModelState();
}
class SpaceModelInitial extends LinkSpaceToModelState {}
class SpaceModelLoading extends LinkSpaceToModelState {}
class SpaceModelSelectedState extends LinkSpaceToModelState {
final int selectedIndex;
const SpaceModelSelectedState(this.selectedIndex);
}
class SpaceModelSelectionUpdated extends LinkSpaceToModelState {
final bool hasSelectedSpaces;
const SpaceModelSelectionUpdated(this.hasSelectedSpaces);
}
class SpaceValidationSuccess extends LinkSpaceToModelState {}
class SpaceModelLinkSuccess extends LinkSpaceToModelState {}
class ValidationError extends LinkSpaceToModelState {
final String message;
const ValidationError(this.message);
}
class SpaceModelOperationFailure extends LinkSpaceToModelState {
final String message;
const SpaceModelOperationFailure(this.message);
}
class AlreadyHaveLinkedState extends LinkSpaceToModelState {
const AlreadyHaveLinkedState();
}

View File

@ -2,9 +2,9 @@ import 'package:flutter/material.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/link_space_model/bloc/link_space_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_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/widgets/space_model_card_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
@ -24,13 +24,13 @@ class LinkSpaceModelDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SpaceModelBloc()
create: (context) => LinkSpaceToModelBloc()
..add(
SpaceModelSelectedEvent(initialSelectedIndex ?? -1),
LinkSpaceModelSelectedEvent(initialSelectedIndex ?? -1),
),
child: Builder(
builder: (context) {
final bloc = context.read<SpaceModelBloc>();
final bloc = context.read<LinkSpaceToModelBloc>();
return AlertDialog(
backgroundColor: ColorsManager.whiteColors,
title: const Text('Link a space model'),
@ -39,7 +39,7 @@ class LinkSpaceModelDialog extends StatelessWidget {
color: ColorsManager.textFieldGreyColor,
width: MediaQuery.of(context).size.width * 0.7,
height: MediaQuery.of(context).size.height * 0.6,
child: BlocBuilder<SpaceModelBloc, SpaceModelState>(
child: BlocBuilder<LinkSpaceToModelBloc, LinkSpaceToModelState>(
builder: (context, state) {
int selectedIndex = -1;
if (state is SpaceModelSelectedState) {
@ -59,7 +59,7 @@ class LinkSpaceModelDialog extends StatelessWidget {
final isSelected = selectedIndex == index;
return GestureDetector(
onTap: () {
bloc.add(SpaceModelSelectedEvent(index));
bloc.add(LinkSpaceModelSelectedEvent(index));
},
child: Container(
margin: const EdgeInsets.all(10.0),
@ -93,7 +93,7 @@ class LinkSpaceModelDialog extends StatelessWidget {
label: 'Cancel',
),
const SizedBox(width: 10),
BlocBuilder<SpaceModelBloc, SpaceModelState>(
BlocBuilder<LinkSpaceToModelBloc, LinkSpaceToModelState>(
builder: (context, state) {
final isEnabled = state is SpaceModelSelectedState &&
state.selectedIndex >= 0;

View File

@ -1,11 +1,11 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.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/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart';
@ -94,14 +94,9 @@ class CreateSpaceModelBloc
orElse: () => subspace,
);
// Update the subspace's tags
final eventTagIds = matchingEventSubspace.tags
?.map((e) => e.internalId)
.toSet() ??
{};
final updatedTags = [
...?subspace.tags?.map<TagModel>((tag) {
...?subspace.tags?.map<Tag>((tag) {
final matchingTag =
matchingEventSubspace.tags?.firstWhere(
(e) => e.internalId == tag.internalId,
@ -112,14 +107,14 @@ class CreateSpaceModelBloc
? tag.copyWith(tag: matchingTag?.tag)
: tag;
}) ??
<TagModel>[],
<Tag>[],
...?matchingEventSubspace.tags?.where(
(e) =>
subspace.tags
?.every((t) => t.internalId != e.internalId) ??
true,
) ??
<TagModel>[],
<Tag>[],
];
return subspace.copyWith(
subspaceName: matchingEventSubspace.subspaceName,
@ -244,7 +239,7 @@ class CreateSpaceModelBloc
}
if (newSubspaces != null) {
for (var newSubspace in newSubspaces!) {
for (var newSubspace in newSubspaces) {
// Tag without UUID
if ((newSubspace.uuid == null || newSubspace.uuid!.isEmpty)) {
final List<TagModelUpdate> tagUpdates = [];
@ -253,7 +248,7 @@ class CreateSpaceModelBloc
for (var tag in newSubspace.tags!) {
tagUpdates.add(TagModelUpdate(
action: Action.add,
uuid: tag.uuid == '' ? null : tag.uuid,
newTagUuid: tag.uuid == '' ? null : tag.uuid,
tag: tag.tag,
productUuid: tag.product?.uuid));
}
@ -268,7 +263,7 @@ class CreateSpaceModelBloc
if (prevSubspaces != null && newSubspaces != null) {
final newSubspaceMap = {
for (var subspace in newSubspaces!) subspace.uuid: subspace
for (var subspace in newSubspaces) subspace.uuid: subspace
};
for (var prevSubspace in prevSubspaces) {
@ -309,8 +304,8 @@ class CreateSpaceModelBloc
}
List<TagModelUpdate> processTagUpdates(
List<TagModel>? prevTags,
List<TagModel>? newTags,
List<Tag>? prevTags,
List<Tag>? newTags,
) {
final List<TagModelUpdate> tagUpdates = [];
final processedTags = <String?>{};
@ -320,7 +315,7 @@ class CreateSpaceModelBloc
tagUpdates.add(TagModelUpdate(
action: Action.add,
tag: newTag.tag,
uuid: newTag.uuid,
newTagUuid: newTag.uuid,
productUuid: newTag.product?.uuid,
));
}
@ -332,7 +327,7 @@ class CreateSpaceModelBloc
if (prevTags != null && newTags != null) {
for (var prevTag in prevTags) {
final existsInNew =
newTags!.any((newTag) => newTag.uuid == prevTag.uuid);
newTags.any((newTag) => newTag.uuid == prevTag.uuid);
if (!existsInNew) {
tagUpdates
.add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid));
@ -349,14 +344,14 @@ class CreateSpaceModelBloc
if (newTags != null) {
final prevTagUuids = prevTags?.map((t) => t.uuid).toSet() ?? {};
for (var newTag in newTags!) {
for (var newTag in newTags) {
// Tag without UUID
if ((newTag.uuid == null || !prevTagUuids.contains(newTag.uuid)) &&
!processedTags.contains(newTag.tag)) {
tagUpdates.add(TagModelUpdate(
action: Action.add,
tag: newTag.tag,
uuid: newTag.uuid == '' ? null : newTag.uuid,
newTagUuid: newTag.uuid == '' ? null : newTag.uuid,
productUuid: newTag.product?.uuid));
processedTags.add(newTag.tag);
}
@ -365,14 +360,15 @@ class CreateSpaceModelBloc
// Case 3: Tags updated
if (prevTags != null && newTags != null) {
final newTagMap = {for (var tag in newTags!) tag.uuid: tag};
final newTagMap = {for (var tag in newTags) tag.uuid: tag};
for (var prevTag in prevTags!) {
for (var prevTag in prevTags) {
final newTag = newTagMap[prevTag.uuid];
if (newTag != null) {
tagUpdates.add(TagModelUpdate(
action: Action.update,
uuid: newTag.uuid,
uuid: prevTag.uuid,
newTagUuid: newTag.uuid,
tag: newTag.tag,
));
} else {}

View File

@ -1,7 +1,7 @@
import 'package:equatable/equatable.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/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
abstract class CreateSpaceModelEvent extends Equatable {
const CreateSpaceModelEvent();
@ -49,7 +49,7 @@ class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent {
}
class AddTagsToSpaceTemplate extends CreateSpaceModelEvent {
final List<TagModel> tags;
final List<Tag> tags;
AddTagsToSpaceTemplate(this.tags);
}

View File

@ -6,7 +6,7 @@ class TagBodyModel {
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'tag': tag,
'name': tag,
'productUuid': productUuid,
};
}

View File

@ -1,6 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.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/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart';
import 'package:uuid/uuid.dart';
@ -9,8 +9,9 @@ class SpaceTemplateModel extends Equatable {
String? uuid;
String modelName;
List<SubspaceTemplateModel>? subspaceModels;
final List<TagModel>? tags;
final List<Tag>? tags;
String internalId;
String? createdAt;
@override
List<Object?> get props => [modelName, subspaceModels, tags];
@ -21,6 +22,7 @@ class SpaceTemplateModel extends Equatable {
required this.modelName,
this.subspaceModels,
this.tags,
this.createdAt,
}) : internalId = internalId ?? const Uuid().v4();
factory SpaceTemplateModel.fromJson(Map<String, dynamic> json) {
@ -28,6 +30,7 @@ class SpaceTemplateModel extends Equatable {
return SpaceTemplateModel(
uuid: json['uuid'] ?? '',
createdAt: json['createdAt'] ?? '',
internalId: internalId,
modelName: json['modelName'] ?? '',
subspaceModels: (json['subspaceModels'] as List<dynamic>?)
@ -38,7 +41,7 @@ class SpaceTemplateModel extends Equatable {
[],
tags: (json['tags'] as List<dynamic>?)
?.where((item) => item is Map<String, dynamic>) // Validate type
.map((item) => TagModel.fromJson(item as Map<String, dynamic>))
.map((item) => Tag.fromJson(item as Map<String, dynamic>))
.toList() ??
[],
);
@ -47,7 +50,7 @@ class SpaceTemplateModel extends Equatable {
String? uuid,
String? modelName,
List<SubspaceTemplateModel>? subspaceModels,
List<TagModel>? tags,
List<Tag>? tags,
String? internalId,
}) {
return SpaceTemplateModel(

View File

@ -1,11 +1,11 @@
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:uuid/uuid.dart';
class SubspaceTemplateModel {
final String? uuid;
String subspaceName;
final bool disabled;
List<TagModel>? tags;
List<Tag>? tags;
String internalId;
SubspaceTemplateModel({
@ -25,7 +25,7 @@ class SubspaceTemplateModel {
internalId: internalId,
disabled: json['disabled'] ?? false,
tags: (json['tags'] as List<dynamic>?)
?.map((item) => TagModel.fromJson(item))
?.map((item) => Tag.fromJson(item))
.toList() ??
[],
);
@ -44,7 +44,7 @@ class SubspaceTemplateModel {
String? uuid,
String? subspaceName,
bool? disabled,
List<TagModel>? tags,
List<Tag>? tags,
String? internalId,
}) {
return SubspaceTemplateModel(

View File

@ -4,7 +4,7 @@ class CreateTagBodyModel {
Map<String, dynamic> toJson() {
return {
'tag': tag,
'name': tag,
'productUuid': productUuid,
};
}

View File

@ -1,65 +0,0 @@
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
import 'package:uuid/uuid.dart';
class TagModel extends BaseTag {
TagModel({
String? uuid,
required String? tag,
ProductModel? product,
String? internalId,
String? location,
}) : super(
uuid: uuid,
tag: tag,
product: product,
internalId: internalId,
location: location,
);
factory TagModel.fromJson(Map<String, dynamic> json) {
final String internalId = json['internalId'] ?? const Uuid().v4();
return TagModel(
uuid: json['uuid'] ,
internalId: internalId,
tag: json['tag'] ?? '',
product: json['product'] != null
? ProductModel.fromMap(json['product'])
: null,
);
}
@override
TagModel copyWith(
{String? tag,
ProductModel? product,
String? uuid,
String? location,
String? internalId}) {
return TagModel(
tag: tag ?? this.tag,
product: product ?? this.product,
location: location ?? this.location,
internalId: internalId ?? this.internalId,
uuid:uuid?? this.uuid
);
}
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'tag': tag,
'product': product?.toMap(),
};
}
}
extension TagModelExtensions on TagModel {
TagBodyModel toTagBodyModel() {
return TagBodyModel()
..uuid = uuid
..tag = tag ?? ''
..productUuid = product?.uuid;
}
}

View File

@ -5,12 +5,14 @@ class TagModelUpdate {
final String? uuid;
final String? tag;
final String? productUuid;
final String? newTagUuid;
TagModelUpdate({
required this.action,
this.uuid,
this.tag,
this.productUuid,
this.newTagUuid,
});
factory TagModelUpdate.fromJson(Map<String, dynamic> json) {
@ -26,9 +28,10 @@ class TagModelUpdate {
Map<String, dynamic> toJson() {
return {
'action': action.value,
'uuid': uuid, // Nullable field
'tag': tag,
'tagUuid': uuid,
'name': tag,
'productUuid': productUuid,
'newTagUuid': newTagUuid
};
}
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
@ -12,8 +13,10 @@ import 'package:syncrow_web/utils/color_manager.dart';
class SpaceModelPage extends StatelessWidget {
final List<ProductModel>? products;
final Function(List<SpaceTemplateModel>)? onSpaceModelsUpdated;
final List<Tag> projectTags;
const SpaceModelPage({Key? key, this.products, this.onSpaceModelsUpdated})
const SpaceModelPage(
{Key? key, this.products, this.onSpaceModelsUpdated, required this.projectTags})
: super(key: key);
@override
@ -60,6 +63,7 @@ class SpaceModelPage extends StatelessWidget {
allTags: allTagValues,
pageContext: context,
otherSpaceModels: allSpaceModelNames,
projectTags: projectTags,
);
},
);
@ -69,8 +73,7 @@ class SpaceModelPage extends StatelessWidget {
}
// Render existing space model
final model = spaceModels[index];
final otherModel =
List<String>.from(allSpaceModelNames);
final otherModel = List<String>.from(allSpaceModelNames);
otherModel.remove(model.modelName);
return GestureDetector(
onTap: () {
@ -84,6 +87,7 @@ class SpaceModelPage extends StatelessWidget {
otherSpaceModels: otherModel,
pageContext: context,
allSpaceModels: spaceModels,
projectTags: projectTags,
);
},
);
@ -107,10 +111,8 @@ class SpaceModelPage extends StatelessWidget {
return Center(
child: Text(
'Error: ${state.message}',
style: Theme.of(context)
.textTheme
.bodySmall
?.copyWith(color: ColorsManager.warningRed),
style:
Theme.of(context).textTheme.bodySmall?.copyWith(color: ColorsManager.warningRed),
),
);
}

View File

@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class ConfirmMergeDialog extends StatelessWidget {
const ConfirmMergeDialog({super.key});
@override
Widget build(BuildContext context) {
return AlertDialog(
backgroundColor: ColorsManager.whiteColors,
title: Center(
child: Text(
'Merge',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.w400, fontSize: 30),
)),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'Are you sure you want to merge?',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.w400, fontSize: 18),
),
const SizedBox(height: 25),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
backgroundColor: ColorsManager.boxColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 3,
),
child: Text(
"Cancel",
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.w400, fontSize: 16),
),
),
),
const SizedBox(width: 10),
Expanded(
child: ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
backgroundColor: ColorsManager.secondaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 3,
),
child: Text(
"Ok",
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 16,
color: ColorsManager.whiteColors,
),
),
),
),
],
),
],
),
);
}
}

View File

@ -0,0 +1,104 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class ConfirmOverwriteDialog extends StatelessWidget {
const ConfirmOverwriteDialog({super.key});
@override
Widget build(BuildContext context) {
return AlertDialog(
backgroundColor: ColorsManager.whiteColors,
title: Center(
child: Text(
'Overwrite',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.w400, fontSize: 30),
)),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'Are you sure you want to overwrite?',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.w400, fontSize: 18),
),
Center(
child: Text(
'Selected spaces already have linked space \nmodel / sub-spaces and devices',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
color: ColorsManager.grayColor),
textAlign: TextAlign.center,
),
),
const SizedBox(height: 25),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
backgroundColor: ColorsManager.boxColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 3,
),
child: Text(
"Cancel",
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.w400, fontSize: 16),
),
),
),
const SizedBox(width: 10),
Expanded(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
showDialog(
context: context,
builder: (BuildContext dialogContext) {
return const LinkingSuccessful();
},
);
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
backgroundColor: ColorsManager.secondaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 3,
),
child: Text(
"Ok",
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 16,
color: ColorsManager.whiteColors,
),
),
),
),
],
),
],
),
);
}
}

View File

@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart';
@ -25,6 +26,7 @@ class CreateSpaceModelDialog extends StatelessWidget {
final BuildContext? pageContext;
final List<String>? otherSpaceModels;
final List<SpaceTemplateModel>? allSpaceModels;
final List<Tag> projectTags;
const CreateSpaceModelDialog(
{Key? key,
@ -33,7 +35,8 @@ class CreateSpaceModelDialog extends StatelessWidget {
this.spaceModel,
this.pageContext,
this.otherSpaceModels,
this.allSpaceModels})
this.allSpaceModels,
required this.projectTags})
: super(key: key);
@override
@ -68,8 +71,7 @@ class CreateSpaceModelDialog extends StatelessWidget {
spaceNameController.addListener(() {
bloc.add(UpdateSpaceTemplateName(
name: spaceNameController.text,
allModels: otherSpaceModels ?? []));
name: spaceNameController.text, allModels: otherSpaceModels ?? []));
});
return bloc;
@ -87,9 +89,7 @@ class CreateSpaceModelDialog extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
Text(
spaceModel?.uuid == null
? 'Create New Space Model'
: 'Edit Space Model',
spaceModel?.uuid == null ? 'Create New Space Model' : 'Edit Space Model',
style: Theme.of(context)
.textTheme
.headlineLarge
@ -101,10 +101,8 @@ class CreateSpaceModelDialog extends StatelessWidget {
child: TextField(
controller: spaceNameController,
onChanged: (value) {
context.read<CreateSpaceModelBloc>().add(
UpdateSpaceTemplateName(
name: value,
allModels: otherSpaceModels ?? []));
context.read<CreateSpaceModelBloc>().add(UpdateSpaceTemplateName(
name: value, allModels: otherSpaceModels ?? []));
},
style: Theme.of(context)
.textTheme
@ -157,6 +155,7 @@ class CreateSpaceModelDialog extends StatelessWidget {
pageContext: pageContext,
otherSpaceModels: otherSpaceModels,
allSpaceModels: allSpaceModels,
projectTags: projectTags,
),
const SizedBox(height: 20),
SizedBox(
@ -179,84 +178,55 @@ class CreateSpaceModelDialog extends StatelessWidget {
!isNameValid)
? null
: () {
final updatedSpaceTemplate =
updatedSpaceModel.copyWith(
modelName:
spaceNameController.text.trim(),
final updatedSpaceTemplate = updatedSpaceModel.copyWith(
modelName: spaceNameController.text.trim(),
);
if (updatedSpaceModel.uuid == null) {
context
.read<CreateSpaceModelBloc>()
.add(
context.read<CreateSpaceModelBloc>().add(
CreateSpaceTemplate(
spaceTemplate:
updatedSpaceTemplate,
spaceTemplate: updatedSpaceTemplate,
onCreate: (newModel) {
if (pageContext != null) {
pageContext!.read<SpaceModelBloc>().add(
CreateSpaceModel(newSpaceModel: newModel));
pageContext!
.read<SpaceModelBloc>()
.add(CreateSpaceModel(
newSpaceModel:
newModel));
pageContext!
.read<
SpaceManagementBloc>()
.add(
UpdateSpaceModelCache(
newModel));
.read<SpaceManagementBloc>()
.add(UpdateSpaceModelCache(newModel));
}
Navigator.of(context)
.pop(); // Close the dialog
Navigator.of(context).pop(); // Close the dialog
},
),
);
} else {
if (pageContext != null) {
final currentState = pageContext!
.read<SpaceModelBloc>()
.state;
if (currentState
is SpaceModelLoaded) {
final spaceModels =
List<SpaceTemplateModel>.from(
currentState.spaceModels);
final currentState =
pageContext!.read<SpaceModelBloc>().state;
if (currentState is SpaceModelLoaded) {
final spaceModels = List<SpaceTemplateModel>.from(
currentState.spaceModels);
final SpaceTemplateModel?
currentSpaceModel = spaceModels
.cast<SpaceTemplateModel?>()
.firstWhere(
(sm) =>
sm?.uuid ==
updatedSpaceModel
.uuid,
final SpaceTemplateModel? currentSpaceModel =
spaceModels.cast<SpaceTemplateModel?>().firstWhere(
(sm) => sm?.uuid == updatedSpaceModel.uuid,
orElse: () => null,
);
if (currentSpaceModel != null) {
context
.read<CreateSpaceModelBloc>()
.add(ModifySpaceTemplate(
spaceTemplate:
currentSpaceModel,
updatedSpaceTemplate:
updatedSpaceTemplate,
spaceTemplate: currentSpaceModel,
updatedSpaceTemplate: updatedSpaceTemplate,
onUpdate: (newModel) {
if (pageContext !=
null) {
pageContext!
.read<
SpaceModelBloc>()
.add(UpdateSpaceModel(
if (pageContext != null) {
pageContext!.read<SpaceModelBloc>().add(
UpdateSpaceModel(
spaceModelUuid:
newModel.uuid ??
''));
newModel.uuid ?? ''));
pageContext!
.read<
SpaceManagementBloc>()
.add(UpdateSpaceModelCache(
newModel));
.read<SpaceManagementBloc>()
.add(UpdateSpaceModelCache(newModel));
}
Navigator.of(context)
.pop();
Navigator.of(context).pop();
}));
}
}
@ -265,11 +235,11 @@ class CreateSpaceModelDialog extends StatelessWidget {
},
backgroundColor: ColorsManager.secondaryColor,
borderRadius: 10,
foregroundColor: ((state.errorMessage != null &&
state.errorMessage != '') ||
!isNameValid)
? ColorsManager.whiteColorsWithOpacity
: ColorsManager.whiteColors,
foregroundColor:
((state.errorMessage != null && state.errorMessage != '') ||
!isNameValid)
? ColorsManager.whiteColorsWithOpacity
: ColorsManager.whiteColors,
child: const Text('OK'),
),
),

View File

@ -0,0 +1,75 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart';
class CustomLoadingIndicator extends StatefulWidget {
@override
_CustomLoadingIndicatorState createState() => _CustomLoadingIndicatorState();
}
class _CustomLoadingIndicatorState extends State<CustomLoadingIndicator>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
)..repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: 50,
height: 50,
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.rotate(
angle: _controller.value * 2 * pi,
child: CustomPaint(
painter: LoadingPainter(),
),
);
},
),
);
}
}
class LoadingPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint()
..strokeWidth = 5
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke;
final double radius = size.width / 2;
final Offset center = Offset(size.width / 2, size.height / 2);
for (int i = 0; i < 12; i++) {
final double angle = (i * 30) * (pi / 180);
final double startX = center.dx + radius * cos(angle);
final double startY = center.dy + radius * sin(angle);
final double endX = center.dx + (radius - 8) * cos(angle);
final double endY = center.dy + (radius - 8) * sin(angle);
paint.color = Colors.blue.withOpacity(i / 12); // Gradient effect
canvas.drawLine(Offset(startX, startY), Offset(endX, endY), paint);
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}

View File

@ -0,0 +1,221 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class LinkSpaceModelSpacesDialog extends StatefulWidget {
final SpaceTemplateModel spaceModel;
const LinkSpaceModelSpacesDialog({super.key, required this.spaceModel});
@override
State<LinkSpaceModelSpacesDialog> createState() =>
_LinkSpaceModelSpacesDialogState();
}
class _LinkSpaceModelSpacesDialogState
extends State<LinkSpaceModelSpacesDialog> {
@override
void initState() {
context.read<LinkSpaceToModelBloc>().add(LinkSpaceModelSelectedIdsEvent());
super.initState();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
contentPadding: EdgeInsets.zero,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
backgroundColor: Colors.white,
content: SizedBox(
width: MediaQuery.of(context).size.width * 0.4,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildDialogContent(),
_buildActionButtons(),
],
),
),
);
}
Widget _buildDialogContent() {
return Expanded(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.4,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Center(
child: Text(
"Link Space Model to Spaces",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.blueAccent,
),
),
),
const Divider(),
const SizedBox(height: 16),
_buildDetailRow(
"Space model name:", widget.spaceModel.modelName),
_buildDetailRow("Creation date and time:",
widget.spaceModel.createdAt.toString()),
_buildDetailRow("Created by:", "Admin"),
const SizedBox(height: 12),
const Text(
"Link to:",
style: TextStyle(fontWeight: FontWeight.bold),
),
const Text(
"Please select all the spaces where you would like to link the Routine.",
style: TextStyle(fontSize: 12, color: Colors.grey),
),
const SizedBox(height: 8),
Expanded(
child: SizedBox(
child: Column(
children: [
Expanded(
flex: 7,
child: Container(
color: ColorsManager.whiteColors,
child: SpaceTreeView(
isSide: true,
onSelect: () {
context
.read<LinkSpaceToModelBloc>()
.add(
LinkSpaceModelSelectedIdsEvent());
})))
],
),
),
),
const SizedBox(
height: 20,
),
],
),
),
),
],
),
),
),
);
}
Widget _buildActionButtons() {
return Row(
children: [
Expanded(
child: Container(
height: 50,
decoration: const BoxDecoration(
border: Border(
right: BorderSide(
color: ColorsManager.grayColor,
width: 0.5,
),
top: BorderSide(
color: ColorsManager.grayColor,
width: 1,
),
),
),
child: _buildButton("Cancel", Colors.grey, () {
Navigator.of(context).pop();
}),
),
),
Expanded(
child: Container(
height: 50,
decoration: const BoxDecoration(
border: Border(
left: BorderSide(
color: ColorsManager.grayColor,
width: 0.5,
),
top: BorderSide(
color: ColorsManager.grayColor,
width: 1.0,
),
),
),
child: _buildButton(
"Confirm",
ColorsManager.onSecondaryColor,
() {
final spaceModelBloc = context.read<LinkSpaceToModelBloc>();
if (!spaceModelBloc.hasSelectedSpaces) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Please select at least one space")),
);
return;
} else {
spaceModelBloc.add(ValidateSpaceModelEvent(context: context));
}
},
),
),
),
],
);
}
}
Widget _buildDetailRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
Expanded(
child: Text(
label,
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
value,
style: const TextStyle(
fontWeight: FontWeight.bold, color: Colors.black),
),
),
],
),
);
}
Widget _buildButton(String text, Color color, VoidCallback onPressed) {
return InkWell(
onTap: onPressed,
child: Center(
child: Text(
text,
style:
TextStyle(color: color, fontWeight: FontWeight.w400, fontSize: 14),
),
),
);
}

View File

@ -0,0 +1,110 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/confirm_merge_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/confirm_overwrite_dialog.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class LinkingAttentionDialog extends StatelessWidget {
const LinkingAttentionDialog({super.key});
@override
Widget build(BuildContext context) {
return AlertDialog(
backgroundColor: ColorsManager.whiteColors,
title: Center(
child: Text(
'Linking Attention',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.w400, fontSize: 30),
)),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'Do you want to merge or overwrite?',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.w400, fontSize: 18),
),
const SizedBox(height: 8),
Text(
'Selected spaces already have commissioned Devices',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.w400, fontSize: 14),
),
const SizedBox(height: 25),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Cancel Button
Expanded(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
showDialog(
context: context,
builder: (BuildContext dialogContext) {
return const ConfirmOverwriteDialog();
},
);
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
backgroundColor: ColorsManager.boxColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 3,
),
child: Text(
"Overwrite",
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.w400, fontSize: 16),
),
),
),
const SizedBox(width: 10),
// OK Button
Expanded(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
showDialog(
context: context,
builder: (BuildContext dialogContext) {
return const ConfirmMergeDialog();
},
);
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
backgroundColor: ColorsManager.boxColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 3,
),
child: Text(
"Merge",
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.w400, fontSize: 16),
),
),
),
],
),
],
),
);
}
}

View File

@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class LinkingSuccessful extends StatelessWidget {
const LinkingSuccessful({super.key});
@override
Widget build(BuildContext context) {
return AlertDialog(
backgroundColor: ColorsManager.whiteColors,
title: Center(
child: SvgPicture.asset(
Assets.successIcon,
)),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'Linking successful',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontWeight: FontWeight.w400, fontSize: 18),
),
const SizedBox(height: 25),
],
),
);
}
}

View File

@ -0,0 +1,110 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
void showOverwriteDialog(
BuildContext context, LinkSpaceToModelBloc bloc, SpaceTemplateModel model) {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return SizedBox(
child: Dialog(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
elevation: 10,
backgroundColor: Colors.white,
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.3,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
"Overwrite",
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
const SizedBox(height: 15),
const Text(
"Are you sure you want to overwrite?",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
textAlign: TextAlign.center,
),
const SizedBox(height: 5),
Text(
"Selected spaces already have linked space model / sub-spaces and devices",
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
textAlign: TextAlign.center,
),
const SizedBox(height: 25),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
backgroundColor: Colors.grey[200],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
),
child: const Text(
"Cancel",
style: TextStyle(
fontSize: 16,
color: Colors.black,
fontWeight: FontWeight.w500,
),
),
),
),
const SizedBox(width: 10),
Expanded(
child: ElevatedButton(
onPressed: () {
bloc.add(LinkSpaceModelEvent(
isOverWrite: true,
selectedSpaceMode: model.uuid));
Navigator.of(context).pop();
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
backgroundColor: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 3,
),
child: const Text(
"OK",
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
),
),
],
),
],
),
),
),
),
);
},
);
}

View File

@ -1,13 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_event.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/dialog/custom_loading_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/link_space_model_spaces_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/linking_successful.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/overwrite_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/delete_space_model_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_product_widget.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart';
@ -69,48 +74,174 @@ class SpaceModelCardWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
model.modelName,
style:
Theme.of(context).textTheme.headlineMedium?.copyWith(
color: Colors.black,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
if (!topActionsDisabled)
GestureDetector(
onTap: () => _showDeleteDialog(context),
child: Container(
width: 36, // Adjust size as needed
height: 36,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
spreadRadius: 2,
blurRadius: 5,
offset: const Offset(0, 2),
),
],
Text(
model.modelName,
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
color: Colors.black,
fontWeight: FontWeight.bold,
),
child: Center(
child: SvgPicture.asset(
Assets.deleteSpaceModel, // Your actual SVG path
width: 20,
height: 20,
colorFilter: const ColorFilter.mode(
Colors.grey, BlendMode.srcIn),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Row(
children: [
InkWell(
onTap: () {
showDialog(
context: context,
builder: (BuildContext dialogContext) {
return BlocProvider<LinkSpaceToModelBloc>(
create: (_) => LinkSpaceToModelBloc(),
child: BlocListener<LinkSpaceToModelBloc,
LinkSpaceToModelState>(
listener: (context, state) {
final _bloc =
BlocProvider.of<LinkSpaceToModelBloc>(
context);
if (state is SpaceModelLoading) {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(20)),
elevation: 10,
backgroundColor: Colors.white,
child: Padding(
padding:
const EdgeInsets.symmetric(
vertical: 30,
horizontal: 50),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CustomLoadingIndicator(),
const SizedBox(height: 20),
const Text(
"Linking in progress",
style: TextStyle(
fontSize: 16,
fontWeight:
FontWeight.w500,
color: Colors.black87,
),
),
],
),
),
);
},
);
} else if (state
is AlreadyHaveLinkedState) {
Navigator.of(dialogContext).pop();
showOverwriteDialog(
context, _bloc, model);
} else if (state
is SpaceValidationSuccess) {
_bloc.add(LinkSpaceModelEvent(
isOverWrite: false,
selectedSpaceMode: model.uuid));
Future.delayed(const Duration(seconds: 1),
() {
Navigator.of(dialogContext).pop();
Navigator.of(dialogContext).pop();
Navigator.of(dialogContext).pop();
});
showDialog(
context: context,
builder: (BuildContext dialogContext) {
return const LinkingSuccessful();
},
).then((v) {
Future.delayed(
const Duration(seconds: 2), () {
Navigator.of(dialogContext).pop();
});
});
} else if (state is SpaceModelLinkSuccess) {
Navigator.of(dialogContext).pop();
Navigator.of(dialogContext).pop();
showDialog(
context: context,
builder: (BuildContext dialogContext) {
return const LinkingSuccessful();
},
);
}
},
child: LinkSpaceModelSpacesDialog(
spaceModel: model,
),
),
);
},
);
},
child: SvgPicture.asset(
Assets.spaceLinkIcon,
fit: BoxFit.contain,
),
),
),
if (!topActionsDisabled)
InkWell(
onTap: () {
_showDeleteDialog(context);
},
child: SvgPicture.asset(
Assets.deleteSpaceLinkIcon,
fit: BoxFit.contain,
),
),
],
),
// Expanded(
// child: Text(
// model.modelName,
// style:
// Theme.of(context).textTheme.headlineMedium?.copyWith(
// color: Colors.black,
// fontWeight: FontWeight.bold,
// ),
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
// ),
// ),
// if (!topActionsDisabled)
// GestureDetector(
// onTap: () => _showDeleteDialog(context),
// child: Container(
// width: 36, // Adjust size as needed
// height: 36,
// decoration: BoxDecoration(
// shape: BoxShape.circle,
// color: Colors.white,
// boxShadow: [
// BoxShadow(
// color: Colors.black.withOpacity(0.1),
// spreadRadius: 2,
// blurRadius: 5,
// offset: const Offset(0, 2),
// ),
// ],
// ),
// child: Center(
// child: SvgPicture.asset(
// Assets.deleteSpaceModel, // Your actual SVG path
// width: 20,
// height: 20,
// colorFilter: const ColorFilter.mode(
// Colors.grey, BlendMode.srcIn),
// ),
// ),
// ),
// ),
],
),
if (!showOnlyName) ...[

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/common/edit_chip.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.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/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart';
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart';
@ -10,9 +10,9 @@ import 'package:syncrow_web/utils/color_manager.dart';
class SubspaceModelCreate extends StatefulWidget {
final List<SubspaceTemplateModel> subspaces;
final void Function(
List<SubspaceTemplateModel> newSubspaces, List<TagModel>? tags)?
List<SubspaceTemplateModel> newSubspaces, List<Tag>? tags)?
onSpaceModelUpdate;
final List<TagModel> tags;
final List<Tag> tags;
const SubspaceModelCreate({
Key? key,
@ -28,7 +28,7 @@ class SubspaceModelCreate extends StatefulWidget {
class _SubspaceModelCreateState extends State<SubspaceModelCreate> {
late List<SubspaceTemplateModel> _subspaces;
String? errorSubspaceId;
late List<TagModel> _tags;
late List<Tag> _tags;
@override
void initState() {
@ -117,7 +117,7 @@ class _SubspaceModelCreateState extends State<SubspaceModelCreate> {
.where((s) => !updatedIds.contains(s.internalId))
.toList();
final List<TagModel> tagsToAppendToSpace = [];
final List<Tag> tagsToAppendToSpace = [];
for (var s in deletedSubspaces) {
if (s.tags != null) {

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/common/edit_chip.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart';
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
@ -20,6 +21,7 @@ class TagChipDisplay extends StatelessWidget {
final BuildContext? pageContext;
final List<String>? otherSpaceModels;
final List<SpaceTemplateModel>? allSpaceModels;
final List<Tag> projectTags;
const TagChipDisplay(BuildContext context,
{Key? key,
@ -31,14 +33,14 @@ class TagChipDisplay extends StatelessWidget {
required this.spaceNameController,
this.pageContext,
this.otherSpaceModels,
this.allSpaceModels})
this.allSpaceModels,
required this.projectTags})
: super(key: key);
@override
Widget build(BuildContext context) {
return (spaceModel?.tags?.isNotEmpty == true ||
spaceModel?.subspaceModels
?.any((subspace) => subspace.tags?.isNotEmpty == true) ==
spaceModel?.subspaceModels?.any((subspace) => subspace.tags?.isNotEmpty == true) ==
true)
? SizedBox(
width: screenWidth * 0.25,
@ -59,8 +61,7 @@ class TagChipDisplay extends StatelessWidget {
// Combine tags from spaceModel and subspaces
...TagHelper.groupTags([
...?spaceModel?.tags,
...?spaceModel?.subspaceModels
?.expand((subspace) => subspace.tags ?? [])
...?spaceModel?.subspaceModels?.expand((subspace) => subspace.tags ?? [])
]).entries.map(
(entry) => Chip(
avatar: SizedBox(
@ -76,9 +77,7 @@ class TagChipDisplay extends StatelessWidget {
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color:
ColorsManager.spaceColor),
.copyWith(color: ColorsManager.spaceColor),
),
backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder(
@ -105,13 +104,12 @@ class TagChipDisplay extends StatelessWidget {
spaceModel: spaceModel,
otherSpaceModels: otherSpaceModels,
initialTags: TagHelper.generateInitialTags(
subspaces: subspaces,
spaceTagModels: spaceModel?.tags ?? []),
subspaces: subspaces, spaceTagModels: spaceModel?.tags ?? []),
title: 'Edit Device',
addedProducts:
TagHelper.createInitialSelectedProducts(
spaceModel?.tags ?? [], subspaces),
addedProducts: TagHelper.createInitialSelectedProducts(
spaceModel?.tags ?? [], subspaces),
spaceName: spaceModel?.modelName ?? '',
projectTags: projectTags,
));
})
],
@ -134,6 +132,7 @@ class TagChipDisplay extends StatelessWidget {
isCreate: true,
spaceModel: spaceModel,
otherSpaceModels: otherSpaceModels,
projectTags: projectTags,
),
);
},

View File

@ -1,6 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
abstract class AddDeviceModelState extends Equatable {
const AddDeviceModelState();
@ -15,7 +15,7 @@ class AddDeviceModelLoading extends AddDeviceModelState {}
class AddDeviceModelLoaded extends AddDeviceModelState {
final List<SelectedProduct> selectedProducts;
final List<TagModel> initialTag;
final List<Tag> initialTag;
const AddDeviceModelLoaded({
required this.selectedProducts,

View File

@ -1,7 +1,7 @@
import 'package:equatable/equatable.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/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
abstract class AddDeviceTypeModelEvent extends Equatable {
const AddDeviceTypeModelEvent();
@ -25,7 +25,7 @@ class UpdateProductCountEvent extends AddDeviceTypeModelEvent {
class InitializeDeviceTypeModel extends AddDeviceTypeModelEvent {
final List<TagModel> initialTags;
final List<Tag> initialTags;
final List<SelectedProduct> addedProducts;
const InitializeDeviceTypeModel({

View File

@ -2,13 +2,13 @@ import 'package:flutter/material.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/tag.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.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/helper/tag_helper.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/tag_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/bloc/add_device_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_state.dart';
@ -20,7 +20,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
final List<ProductModel>? products;
final List<SelectedProduct>? initialSelectedProducts;
final List<SubspaceTemplateModel>? subspaces;
final List<TagModel>? spaceTagModels;
final List<Tag>? spaceTagModels;
final List<String>? allTags;
final String spaceName;
final bool isCreate;
@ -28,6 +28,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
final BuildContext? pageContext;
final SpaceTemplateModel? spaceModel;
final List<SpaceTemplateModel>? allSpaceModels;
final List<Tag> projectTags;
const AddDeviceTypeModelWidget(
{super.key,
@ -41,7 +42,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
this.pageContext,
this.otherSpaceModels,
this.spaceModel,
this.allSpaceModels});
this.allSpaceModels,
required this.projectTags});
@override
Widget build(BuildContext context) {
@ -78,8 +80,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
const SizedBox(height: 16),
Expanded(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20.0),
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: ScrollableGridViewWidget(
isCreate: isCreate,
products: products,
@ -112,6 +113,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
allSpaceModels: allSpaceModels,
products: products,
allTags: allTags,
projectTags: projectTags,
pageContext: pageContext,
otherSpaceModels: otherSpaceModels,
spaceModel: SpaceTemplateModel(
@ -137,6 +139,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
subspaces: subspaces,
addedProducts: initialSelectedProducts ?? [],
allTags: allTags,
projectTags: projectTags,
spaceName: spaceName,
initialTags: initialTags,
otherSpaceModels: otherSpaceModels,
@ -149,11 +152,10 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
),
SizedBox(
width: 140,
child:
BlocBuilder<AddDeviceTypeModelBloc, AddDeviceModelState>(
child: BlocBuilder<AddDeviceTypeModelBloc, AddDeviceModelState>(
builder: (context, state) {
final isDisabled = state is AddDeviceModelLoaded &&
state.selectedProducts.isEmpty;
final isDisabled =
state is AddDeviceModelLoaded && state.selectedProducts.isEmpty;
return DefaultButton(
backgroundColor: ColorsManager.secondaryColor,
@ -166,15 +168,13 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
: () async {
if (state is AddDeviceModelLoaded &&
state.selectedProducts.isNotEmpty) {
final initialTags =
TagHelper.generateInitialTags(
final initialTags = TagHelper.generateInitialTags(
spaceTagModels: spaceTagModels,
subspaces: subspaces,
);
final dialogTitle = initialTags.isNotEmpty
? 'Edit Device'
: 'Assign Tags';
final dialogTitle =
initialTags.isNotEmpty ? 'Edit Device' : 'Assign Tags';
Navigator.of(context).pop();
await showDialog<bool>(
context: context,
@ -184,6 +184,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
subspaces: subspaces,
addedProducts: state.selectedProducts,
allTags: allTags,
projectTags: projectTags,
spaceName: spaceName,
initialTags: initialTags,
otherSpaceModels: otherSpaceModels,

View File

@ -9,10 +9,10 @@ import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_
import 'package:syncrow_web/pages/visitor_password/model/device_model.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/temp_const.dart';
class DevicesManagementApi {
Future<List<AllDevicesModel>> fetchDevices(String communityId, String spaceId, String projectId) async {
Future<List<AllDevicesModel>> fetchDevices(
String communityId, String spaceId, String projectId) async {
try {
final response = await HTTPService().get(
path: communityId.isNotEmpty && spaceId.isNotEmpty

View File

@ -1,8 +1,8 @@
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/space_template_model.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/temp_const.dart';
class SpaceModelManagementApi {
Future<List<SpaceTemplateModel>> listSpaceModels(
@ -33,8 +33,8 @@ class SpaceModelManagementApi {
return response;
}
Future<String?> updateSpaceModel(CreateSpaceTemplateBodyModel spaceModel,
String spaceModelUuid, String projectId) async {
Future<String?> updateSpaceModel(
CreateSpaceTemplateBodyModel spaceModel, String spaceModelUuid, String projectId) async {
final response = await HTTPService().put(
path: ApiEndpoints.updateSpaceModel
.replaceAll('{projectId}', projectId)
@ -47,8 +47,7 @@ class SpaceModelManagementApi {
return response;
}
Future<SpaceTemplateModel?> getSpaceModel(
String spaceModelUuid, String projectId) async {
Future<SpaceTemplateModel?> getSpaceModel(String spaceModelUuid, String projectId) async {
final response = await HTTPService().get(
path: ApiEndpoints.getSpaceModel
.replaceAll('{projectId}', projectId)
@ -61,6 +60,36 @@ class SpaceModelManagementApi {
return response;
}
Future linkSpaceModel(
{required String spaceModelUuid,
required String projectId,
required List<String> spaceUuids,
required bool isOverWrite}) async {
final response = await HTTPService().post(
path: ApiEndpoints.linkSpaceModel
.replaceAll('{projectId}', projectId)
.replaceAll('{spaceModelUuid}', spaceModelUuid),
showServerMessage: true,
body: {"spaceUuids": spaceUuids, "overwrite": isOverWrite},
expectedResponseModel: (json) {
return json;
},
);
return response;
}
Future validateSpaceModel(String projectId, List<String> spaceUuids) async {
final response = await HTTPService().post(
path: ApiEndpoints.validateSpaceModel
.replaceAll('{projectId}', projectId),
showServerMessage: true,
body: {"spacesUuids": spaceUuids},
expectedResponseModel: (json) {
return json;
});
return response;
}
Future<bool> deleteSpaceModel(String spaceModelUuid, String projectId) async {
final response = await HTTPService().delete(
path: ApiEndpoints.getSpaceModel
@ -73,4 +102,17 @@ class SpaceModelManagementApi {
);
return response;
}
Future<List<Tag>> listTags({required String projectId}) async {
final response = await HTTPService().get(
path: ApiEndpoints.listTags.replaceAll('{projectId}', projectId),
expectedResponseModel: (json) {
List<dynamic> jsonData = json['data'];
return jsonData.map((jsonItem) {
return Tag.fromJson(jsonItem);
}).toList();
},
);
return response;
}
}

View File

@ -52,7 +52,7 @@ abstract class ColorsManager {
static const Color semiTransparentBlackColor = Color(0x3F000000);
static const Color transparentColor = Color(0x00000000);
static const Color spaceColor = Color(0xB2023DFE);
static const Color counterBackgroundColor = Color(0xCCF4F4F4);
static const Color counterBackgroundColor = Color.fromARGB(204, 105, 2, 2);
static const Color neutralGray = Color(0xFFE5E5E5);
static const Color warningRed = Color(0xFFFF6465);
static const Color borderColor = Color(0xFFE5E5E5);

View File

@ -89,6 +89,13 @@ abstract class ApiEndpoints {
static const String createSpaceModel = '/projects/{projectId}/space-models';
static const String getSpaceModel = '/projects/{projectId}/space-models/{spaceModelUuid}';
static const String updateSpaceModel = '/projects/{projectId}/space-models/{spaceModelUuid}';
//tag
static const String listTags = '/projects/{projectId}/tags';
static const String linkSpaceModel =
'/projects/{projectId}/space-models/{spaceModelUuid}/spaces/link';
static const String validateSpaceModel = '/projects/{projectId}/spaces/validate';
static const String roleTypes = '/role/types';
static const String permission = '/permission/{roleUuid}';

View File

@ -402,5 +402,10 @@ class Assets {
static const String link = 'assets/icons/link.svg';
static const String duplicate = 'assets/icons/duplicate.svg';
static const String spaceDelete = 'assets/icons/space_delete.svg';
static const String deleteSpaceLinkIcon =
'assets/icons/delete_space_link_icon.svg';
static const String spaceLinkIcon = 'assets/icons/space_link_icon.svg';
static const String successIcon = 'assets/icons/success_icon.svg';
}
//user_management.svg