mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-11-27 08:24:56 +00:00
added subspaces
This commit is contained in:
@ -1,6 +1,8 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
@ -22,6 +24,8 @@ class SpaceModel {
|
|||||||
SpaceStatus status;
|
SpaceStatus status;
|
||||||
String internalId;
|
String internalId;
|
||||||
SpaceTemplateModel? spaceModel;
|
SpaceTemplateModel? spaceModel;
|
||||||
|
final List<Tag>? tags;
|
||||||
|
List<SubspaceModel>? subspaces;
|
||||||
|
|
||||||
List<Connection> outgoingConnections = []; // Connections from this space
|
List<Connection> outgoingConnections = []; // Connections from this space
|
||||||
Connection? incomingConnection; // Connections to this space
|
Connection? incomingConnection; // Connections to this space
|
||||||
@ -42,6 +46,8 @@ class SpaceModel {
|
|||||||
this.incomingConnection,
|
this.incomingConnection,
|
||||||
this.status = SpaceStatus.unchanged,
|
this.status = SpaceStatus.unchanged,
|
||||||
this.spaceModel,
|
this.spaceModel,
|
||||||
|
this.tags,
|
||||||
|
this.subspaces,
|
||||||
}) : internalId = internalId ?? const Uuid().v4();
|
}) : internalId = internalId ?? const Uuid().v4();
|
||||||
|
|
||||||
factory SpaceModel.fromJson(Map<String, dynamic> json,
|
factory SpaceModel.fromJson(Map<String, dynamic> json,
|
||||||
@ -64,6 +70,11 @@ class SpaceModel {
|
|||||||
name: json['spaceName'],
|
name: json['spaceName'],
|
||||||
isPrivate: json['isPrivate'] ?? false,
|
isPrivate: json['isPrivate'] ?? false,
|
||||||
invitationCode: json['invitationCode'],
|
invitationCode: json['invitationCode'],
|
||||||
|
subspaces: (json['subspaces'] as List<dynamic>?)
|
||||||
|
?.where((e) => e is Map<String, dynamic>) // Validate type
|
||||||
|
.map((e) => SubspaceModel.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
[],
|
||||||
parent: parentInternalId != null
|
parent: parentInternalId != null
|
||||||
? SpaceModel(
|
? SpaceModel(
|
||||||
internalId: parentInternalId,
|
internalId: parentInternalId,
|
||||||
@ -85,6 +96,11 @@ class SpaceModel {
|
|||||||
icon: json['icon'] ?? Assets.location,
|
icon: json['icon'] ?? Assets.location,
|
||||||
position: Offset(json['x'] ?? 0, json['y'] ?? 0),
|
position: Offset(json['x'] ?? 0, json['y'] ?? 0),
|
||||||
isHovered: false,
|
isHovered: false,
|
||||||
|
tags: (json['tags'] as List<dynamic>?)
|
||||||
|
?.where((item) => item is Map<String, dynamic>) // Validate type
|
||||||
|
.map((item) => Tag.fromJson(item as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (json['incomingConnections'] != null &&
|
if (json['incomingConnections'] != null &&
|
||||||
@ -110,6 +126,7 @@ class SpaceModel {
|
|||||||
'isPrivate': isPrivate,
|
'isPrivate': isPrivate,
|
||||||
'invitationCode': invitationCode,
|
'invitationCode': invitationCode,
|
||||||
'parent': parent?.uuid,
|
'parent': parent?.uuid,
|
||||||
|
'subspaces': subspaces?.map((e) => e.toJson()).toList(),
|
||||||
'community': community?.toMap(),
|
'community': community?.toMap(),
|
||||||
'children': children.map((child) => child.toMap()).toList(),
|
'children': children.map((child) => child.toMap()).toList(),
|
||||||
'icon': icon,
|
'icon': icon,
|
||||||
@ -117,6 +134,7 @@ class SpaceModel {
|
|||||||
'isHovered': isHovered,
|
'isHovered': isHovered,
|
||||||
'outgoingConnections': outgoingConnections.map((c) => c.toMap()).toList(),
|
'outgoingConnections': outgoingConnections.map((c) => c.toMap()).toList(),
|
||||||
'incomingConnection': incomingConnection?.toMap(),
|
'incomingConnection': incomingConnection?.toMap(),
|
||||||
|
'tags': tags?.map((e) => e.toJson()).toList(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,3 +142,28 @@ class SpaceModel {
|
|||||||
outgoingConnections.add(connection);
|
outgoingConnections.add(connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension SpaceExtensions on SpaceModel {
|
||||||
|
List<String> listAllTagValues() {
|
||||||
|
final List<String> tagValues = [];
|
||||||
|
|
||||||
|
if (tags != null) {
|
||||||
|
tagValues.addAll(
|
||||||
|
tags!.map((tag) => tag.tag ?? '').where((tag) => tag.isNotEmpty));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subspaces != null) {
|
||||||
|
for (final subspace in subspaces!) {
|
||||||
|
if (subspace.tags != null) {
|
||||||
|
tagValues.addAll(
|
||||||
|
subspace.tags!
|
||||||
|
.map((tag) => tag.tag ?? '')
|
||||||
|
.where((tag) => tag.isNotEmpty),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tagValues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
110
lib/pages/spaces_management/all_spaces/model/subspace_model.dart
Normal file
110
lib/pages/spaces_management/all_spaces/model/subspace_model.dart
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||||
|
import 'package:syncrow_web/utils/constants/action_enum.dart';
|
||||||
|
|
||||||
|
import 'tag.dart';
|
||||||
|
|
||||||
|
class SubspaceModel {
|
||||||
|
final String? uuid;
|
||||||
|
String subspaceName;
|
||||||
|
final bool disabled;
|
||||||
|
List<Tag>? tags;
|
||||||
|
|
||||||
|
SubspaceModel({
|
||||||
|
this.uuid,
|
||||||
|
required this.subspaceName,
|
||||||
|
required this.disabled,
|
||||||
|
this.tags,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SubspaceModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return SubspaceModel(
|
||||||
|
uuid: json['uuid'] ?? '',
|
||||||
|
subspaceName: json['subspaceName'] ?? '',
|
||||||
|
disabled: json['disabled'] ?? false,
|
||||||
|
tags: (json['tags'] as List<dynamic>?)
|
||||||
|
?.map((item) => Tag.fromJson(item))
|
||||||
|
.toList() ??
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'uuid': uuid,
|
||||||
|
'subspaceName': subspaceName,
|
||||||
|
'disabled': disabled,
|
||||||
|
'tags': tags?.map((e) => e.toJson()).toList() ?? [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateSubspaceModel {
|
||||||
|
final String uuid;
|
||||||
|
final Action action;
|
||||||
|
final String? subspaceName;
|
||||||
|
final List<UpdateTag>? tags;
|
||||||
|
UpdateSubspaceModel({
|
||||||
|
required this.action,
|
||||||
|
required this.uuid,
|
||||||
|
this.subspaceName,
|
||||||
|
this.tags,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory UpdateSubspaceModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return UpdateSubspaceModel(
|
||||||
|
action: ActionExtension.fromValue(json['action']),
|
||||||
|
uuid: json['uuid'] ?? '',
|
||||||
|
subspaceName: json['subspaceName'] ?? '',
|
||||||
|
tags: (json['tags'] as List)
|
||||||
|
.map((item) => UpdateTag.fromJson(item))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'action': action.value,
|
||||||
|
'uuid': uuid,
|
||||||
|
'subspaceName': subspaceName,
|
||||||
|
'tags': tags?.map((e) => e.toJson()).toList() ?? [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateTag {
|
||||||
|
final Action action;
|
||||||
|
final String? uuid;
|
||||||
|
final String tag;
|
||||||
|
final bool disabled;
|
||||||
|
final ProductModel? product;
|
||||||
|
|
||||||
|
UpdateTag({
|
||||||
|
required this.action,
|
||||||
|
this.uuid,
|
||||||
|
required this.tag,
|
||||||
|
required this.disabled,
|
||||||
|
this.product,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory UpdateTag.fromJson(Map<String, dynamic> json) {
|
||||||
|
return UpdateTag(
|
||||||
|
action: ActionExtension.fromValue(json['action']),
|
||||||
|
uuid: json['uuid'] ?? '',
|
||||||
|
tag: json['tag'] ?? '',
|
||||||
|
disabled: json['disabled'] ?? false,
|
||||||
|
product: json['product'] != null
|
||||||
|
? ProductModel.fromMap(json['product'])
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'action': action.value,
|
||||||
|
'uuid': uuid,
|
||||||
|
'tag': tag,
|
||||||
|
'disabled': disabled,
|
||||||
|
'product': product?.toMap(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
61
lib/pages/spaces_management/all_spaces/model/tag.dart
Normal file
61
lib/pages/spaces_management/all_spaces/model/tag.dart
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
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 Tag {
|
||||||
|
String? uuid;
|
||||||
|
String? tag;
|
||||||
|
final ProductModel? product;
|
||||||
|
String internalId;
|
||||||
|
String? location;
|
||||||
|
|
||||||
|
Tag(
|
||||||
|
{this.uuid,
|
||||||
|
required this.tag,
|
||||||
|
this.product,
|
||||||
|
String? internalId,
|
||||||
|
this.location})
|
||||||
|
: internalId = internalId ?? const Uuid().v4();
|
||||||
|
|
||||||
|
factory Tag.fromJson(Map<String, dynamic> json) {
|
||||||
|
final String internalId = json['internalId'] ?? const Uuid().v4();
|
||||||
|
|
||||||
|
return Tag(
|
||||||
|
uuid: json['uuid'] ?? '',
|
||||||
|
internalId: internalId,
|
||||||
|
tag: json['tag'] ?? '',
|
||||||
|
product: json['product'] != null
|
||||||
|
? ProductModel.fromMap(json['product'])
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Tag copyWith({
|
||||||
|
String? tag,
|
||||||
|
ProductModel? product,
|
||||||
|
String? location,
|
||||||
|
}) {
|
||||||
|
return Tag(
|
||||||
|
tag: tag ?? this.tag,
|
||||||
|
product: product ?? this.product,
|
||||||
|
location: location ?? this.location,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'uuid': uuid,
|
||||||
|
'tag': tag,
|
||||||
|
'product': product?.toMap(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TagModelExtensions on Tag {
|
||||||
|
TagBodyModel toTagBodyModel() {
|
||||||
|
return TagBodyModel()
|
||||||
|
..uuid = uuid ?? ''
|
||||||
|
..tag = tag ?? ''
|
||||||
|
..productUuid = product?.uuid;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,8 +5,11 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
|||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart';
|
import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
@ -24,6 +27,7 @@ class CreateSpaceDialog extends StatefulWidget {
|
|||||||
final SpaceModel? parentSpace;
|
final SpaceModel? parentSpace;
|
||||||
final SpaceModel? editSpace;
|
final SpaceModel? editSpace;
|
||||||
final List<SpaceTemplateModel>? spaceModels;
|
final List<SpaceTemplateModel>? spaceModels;
|
||||||
|
final List<SubspaceModel>? subspaces;
|
||||||
|
|
||||||
const CreateSpaceDialog(
|
const CreateSpaceDialog(
|
||||||
{super.key,
|
{super.key,
|
||||||
@ -35,7 +39,8 @@ class CreateSpaceDialog extends StatefulWidget {
|
|||||||
this.isEdit = false,
|
this.isEdit = false,
|
||||||
this.editSpace,
|
this.editSpace,
|
||||||
this.selectedProducts = const [],
|
this.selectedProducts = const [],
|
||||||
this.spaceModels});
|
this.spaceModels,
|
||||||
|
this.subspaces});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
CreateSpaceDialogState createState() => CreateSpaceDialogState();
|
CreateSpaceDialogState createState() => CreateSpaceDialogState();
|
||||||
@ -50,6 +55,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
bool isOkButtonEnabled = false;
|
bool isOkButtonEnabled = false;
|
||||||
bool isNameFieldInvalid = false;
|
bool isNameFieldInvalid = false;
|
||||||
bool isNameFieldExist = false;
|
bool isNameFieldExist = false;
|
||||||
|
List<SubspaceModel>? subspaces;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -202,8 +208,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
if (selectedSpaceModel == null)
|
selectedSpaceModel == null
|
||||||
DefaultButton(
|
? DefaultButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_showLinkSpaceModelDialog(context);
|
_showLinkSpaceModelDialog(context);
|
||||||
},
|
},
|
||||||
@ -221,8 +227,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
padding: const EdgeInsets.only(left: 6.0),
|
padding: const EdgeInsets.only(left: 6.0),
|
||||||
child: SvgPicture.asset(
|
child: SvgPicture.asset(
|
||||||
Assets.link,
|
Assets.link,
|
||||||
width:
|
width: screenWidth *
|
||||||
screenWidth * 0.015, // Adjust icon size
|
0.015, // Adjust icon size
|
||||||
height: screenWidth * 0.015,
|
height: screenWidth * 0.015,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -230,17 +236,18 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Link a space model',
|
'Link a space model',
|
||||||
overflow:
|
overflow: TextOverflow
|
||||||
TextOverflow.ellipsis, // Prevent overflow
|
.ellipsis, // Prevent overflow
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
if (selectedSpaceModel != null)
|
: Container(
|
||||||
Container(
|
|
||||||
width: screenWidth * 0.35,
|
width: screenWidth * 0.35,
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 10.0, horizontal: 16.0),
|
vertical: 10.0, horizontal: 16.0),
|
||||||
@ -285,7 +292,6 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
onDeleted: () => setState(() {
|
onDeleted: () => setState(() {
|
||||||
this.selectedSpaceModel = null;
|
this.selectedSpaceModel = null;
|
||||||
})),
|
})),
|
||||||
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -317,8 +323,12 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 25),
|
const SizedBox(height: 25),
|
||||||
DefaultButton(
|
subspaces == null
|
||||||
onPressed: () {},
|
? DefaultButton(
|
||||||
|
onPressed: () {
|
||||||
|
_showSubSpaceModelDialog(context, enteredName, [],
|
||||||
|
false, widget.products, subspaces);
|
||||||
|
},
|
||||||
backgroundColor: ColorsManager.textFieldGreyColor,
|
backgroundColor: ColorsManager.textFieldGreyColor,
|
||||||
foregroundColor: Colors.black,
|
foregroundColor: Colors.black,
|
||||||
borderColor: ColorsManager.neutralGray,
|
borderColor: ColorsManager.neutralGray,
|
||||||
@ -333,7 +343,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
padding: const EdgeInsets.only(left: 6.0),
|
padding: const EdgeInsets.only(left: 6.0),
|
||||||
child: SvgPicture.asset(
|
child: SvgPicture.asset(
|
||||||
Assets.addIcon,
|
Assets.addIcon,
|
||||||
width: screenWidth * 0.015, // Adjust icon size
|
width: screenWidth *
|
||||||
|
0.015, // Adjust icon size
|
||||||
height: screenWidth * 0.015,
|
height: screenWidth * 0.015,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -341,9 +352,76 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Create sub space',
|
'Create sub space',
|
||||||
overflow:
|
overflow: TextOverflow
|
||||||
TextOverflow.ellipsis, // Prevent overflow
|
.ellipsis, // Prevent overflow
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SizedBox(
|
||||||
|
width: screenWidth * 0.35,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.textFieldGreyColor,
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorsManager.textFieldGreyColor,
|
||||||
|
width: 3.0, // Border width
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 8.0,
|
||||||
|
runSpacing: 8.0,
|
||||||
|
children: [
|
||||||
|
if (subspaces != null)
|
||||||
|
...subspaces!.map(
|
||||||
|
(subspace) => Chip(
|
||||||
|
label: Text(
|
||||||
|
subspace.subspaceName,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorsManager
|
||||||
|
.spaceColor), // Text color
|
||||||
|
),
|
||||||
|
backgroundColor: ColorsManager
|
||||||
|
.whiteColors, // Chip background color
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
16), // Rounded chip
|
||||||
|
side: const BorderSide(
|
||||||
|
color: ColorsManager
|
||||||
|
.spaceColor), // Border color
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
_showSubSpaceModelDialog(
|
||||||
|
context,
|
||||||
|
enteredName,
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
widget.products,
|
||||||
|
subspaces);
|
||||||
|
},
|
||||||
|
child: Chip(
|
||||||
|
label: const Text(
|
||||||
|
'Edit',
|
||||||
|
style: TextStyle(
|
||||||
|
color: ColorsManager.spaceColor),
|
||||||
|
),
|
||||||
|
backgroundColor:
|
||||||
|
ColorsManager.whiteColors,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
side: const BorderSide(
|
||||||
|
color: ColorsManager.spaceColor),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -481,15 +559,40 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
onSave: (selectedModel) {
|
onSave: (selectedModel) {
|
||||||
if (selectedModel != null) {
|
if (selectedModel != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
this.selectedSpaceModel = selectedModel;
|
selectedSpaceModel = selectedModel;
|
||||||
});
|
});
|
||||||
print('Selected Model: ${selectedModel.modelName}');
|
|
||||||
} else {
|
|
||||||
print('No model selected');
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showSubSpaceModelDialog(
|
||||||
|
BuildContext context,
|
||||||
|
String name,
|
||||||
|
final List<Tag>? spaceTags,
|
||||||
|
bool isEdit,
|
||||||
|
List<ProductModel>? products,
|
||||||
|
final List<SubspaceModel>? existingSubSpaces) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return CreateSubSpaceDialog(
|
||||||
|
spaceName: name,
|
||||||
|
dialogTitle: 'Create Sub-space',
|
||||||
|
spaceTags: spaceTags,
|
||||||
|
isEdit: isEdit,
|
||||||
|
products: products,
|
||||||
|
existingSubSpaces: existingSubSpaces,
|
||||||
|
onSave: (slectedSubspaces) {
|
||||||
|
if (slectedSubspaces != null) {
|
||||||
|
setState(() {
|
||||||
|
subspaces = slectedSubspaces;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,57 @@
|
|||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||||
|
import 'package:syncrow_web/utils/constants/action_enum.dart';
|
||||||
|
|
||||||
|
import 'subspace_event.dart';
|
||||||
|
import 'subspace_state.dart';
|
||||||
|
|
||||||
|
class SubSpaceBloc extends Bloc<SubSpaceEvent, SubSpaceState> {
|
||||||
|
SubSpaceBloc() : super(SubSpaceState([], [], '')) {
|
||||||
|
on<AddSubSpace>((event, emit) {
|
||||||
|
final existingNames =
|
||||||
|
state.subSpaces.map((e) => e.subspaceName).toSet();
|
||||||
|
|
||||||
|
if (existingNames.contains(event.subSpace.subspaceName.toLowerCase())) {
|
||||||
|
emit(SubSpaceState(
|
||||||
|
state.subSpaces,
|
||||||
|
state.updatedSubSpaceModels,
|
||||||
|
'Subspace name already exists.',
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
final updatedSubSpaces = List<SubspaceModel>.from(state.subSpaces)
|
||||||
|
..add(event.subSpace);
|
||||||
|
|
||||||
|
emit(SubSpaceState(
|
||||||
|
updatedSubSpaces,
|
||||||
|
state.updatedSubSpaceModels,
|
||||||
|
'',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle RemoveSubSpace Event
|
||||||
|
on<RemoveSubSpace>((event, emit) {
|
||||||
|
final updatedSubSpaces = List<SubspaceModel>.from(state.subSpaces)
|
||||||
|
..remove(event.subSpace);
|
||||||
|
|
||||||
|
final updatedSubspaceModels = List<UpdateSubspaceModel>.from(
|
||||||
|
state.updatedSubSpaceModels,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (event.subSpace.uuid?.isNotEmpty ?? false) {
|
||||||
|
updatedSubspaceModels.add(UpdateSubspaceModel(
|
||||||
|
action: Action.delete,
|
||||||
|
uuid: event.subSpace.uuid!,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(SubSpaceState(
|
||||||
|
updatedSubSpaces,
|
||||||
|
updatedSubspaceModels,
|
||||||
|
'', // Clear error message
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle UpdateSubSpace Event
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||||
|
|
||||||
|
abstract class SubSpaceEvent {}
|
||||||
|
|
||||||
|
class AddSubSpace extends SubSpaceEvent {
|
||||||
|
final SubspaceModel subSpace;
|
||||||
|
AddSubSpace(this.subSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoveSubSpace extends SubSpaceEvent {
|
||||||
|
final SubspaceModel subSpace;
|
||||||
|
RemoveSubSpace(this.subSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateSubSpace extends SubSpaceEvent {
|
||||||
|
final SubspaceModel updatedSubSpace;
|
||||||
|
UpdateSubSpace(this.updatedSubSpace);
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||||
|
|
||||||
|
class SubSpaceState {
|
||||||
|
final List<SubspaceModel> subSpaces;
|
||||||
|
final List<UpdateSubspaceModel> updatedSubSpaceModels;
|
||||||
|
final String errorMessage;
|
||||||
|
|
||||||
|
SubSpaceState(
|
||||||
|
this.subSpaces,
|
||||||
|
this.updatedSubSpaceModels,
|
||||||
|
this.errorMessage,
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
SubSpaceState copyWith({
|
||||||
|
List<SubspaceModel>? subSpaces,
|
||||||
|
List<UpdateSubspaceModel>? updatedSubSpaceModels,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return SubSpaceState(
|
||||||
|
subSpaces ?? this.subSpaces,
|
||||||
|
updatedSubSpaceModels ?? this.updatedSubSpaceModels,
|
||||||
|
errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,196 @@
|
|||||||
|
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/product_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_event.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_state.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class CreateSubSpaceDialog extends StatelessWidget {
|
||||||
|
final bool isEdit;
|
||||||
|
final String dialogTitle;
|
||||||
|
final List<SubspaceModel>? existingSubSpaces;
|
||||||
|
final String? spaceName;
|
||||||
|
final List<Tag>? spaceTags;
|
||||||
|
final List<ProductModel>? products;
|
||||||
|
final Function(List<SubspaceModel>?)? onSave;
|
||||||
|
|
||||||
|
const CreateSubSpaceDialog(
|
||||||
|
{Key? key,
|
||||||
|
required this.isEdit,
|
||||||
|
required this.dialogTitle,
|
||||||
|
this.existingSubSpaces,
|
||||||
|
required this.spaceName,
|
||||||
|
required this.spaceTags,
|
||||||
|
required this.products,
|
||||||
|
required this.onSave})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
final textController = TextEditingController();
|
||||||
|
|
||||||
|
return Dialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: BlocProvider(
|
||||||
|
create: (_) {
|
||||||
|
final bloc = SubSpaceBloc();
|
||||||
|
if (existingSubSpaces != null) {
|
||||||
|
for (var subSpace in existingSubSpaces!) {
|
||||||
|
bloc.add(AddSubSpace(subSpace));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bloc;
|
||||||
|
},
|
||||||
|
child: BlocBuilder<SubSpaceBloc, SubSpaceState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Container(
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
child: SizedBox(
|
||||||
|
width: screenWidth * 0.35,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
dialogTitle,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.headlineLarge
|
||||||
|
?.copyWith(color: ColorsManager.blackColor),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Container(
|
||||||
|
width: screenWidth * 0.35,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 10.0, horizontal: 16.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.boxColor,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 8.0,
|
||||||
|
runSpacing: 8.0,
|
||||||
|
children: [
|
||||||
|
...state.subSpaces.map(
|
||||||
|
(subSpace) => Chip(
|
||||||
|
label: Text(
|
||||||
|
subSpace.subspaceName,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorsManager.spaceColor),
|
||||||
|
),
|
||||||
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
side: const BorderSide(
|
||||||
|
color: ColorsManager.transparentColor,
|
||||||
|
width: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
deleteIcon: Container(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorsManager.lightGrayColor,
|
||||||
|
width: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 16,
|
||||||
|
color: ColorsManager.lightGrayColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onDeleted: () => context
|
||||||
|
.read<SubSpaceBloc>()
|
||||||
|
.add(RemoveSubSpace(subSpace)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 200,
|
||||||
|
child: TextField(
|
||||||
|
controller: textController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
hintText: state.subSpaces.isEmpty
|
||||||
|
? 'Please enter the name'
|
||||||
|
: null,
|
||||||
|
hintStyle: const TextStyle(
|
||||||
|
color: ColorsManager.lightGrayColor),
|
||||||
|
),
|
||||||
|
onSubmitted: (value) {
|
||||||
|
if (value.trim().isNotEmpty) {
|
||||||
|
context.read<SubSpaceBloc>().add(
|
||||||
|
AddSubSpace(SubspaceModel(
|
||||||
|
subspaceName: value.trim(),
|
||||||
|
disabled: false)));
|
||||||
|
textController.clear();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorsManager.blackColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (state.errorMessage.isNotEmpty)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
|
child: Text(
|
||||||
|
state.errorMessage,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorsManager.warningRed,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: CancelButton(
|
||||||
|
label: 'Cancel',
|
||||||
|
onPressed: () async {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: DefaultButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final subSpaces = context
|
||||||
|
.read<SubSpaceBloc>()
|
||||||
|
.state
|
||||||
|
.subSpaces;
|
||||||
|
onSave!(subSpaces);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
backgroundColor: ColorsManager.secondaryColor,
|
||||||
|
borderRadius: 10,
|
||||||
|
foregroundColor: ColorsManager.whiteColors,
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -106,9 +106,8 @@ class LinkSpaceModelDialog extends StatelessWidget {
|
|||||||
? () {
|
? () {
|
||||||
if (onSave != null) {
|
if (onSave != null) {
|
||||||
final selectedModel =
|
final selectedModel =
|
||||||
state is SpaceModelSelectedState
|
spaceModels[state.selectedIndex];
|
||||||
? spaceModels[state.selectedIndex]
|
|
||||||
: null;
|
|
||||||
onSave!(selectedModel);
|
onSave!(selectedModel);
|
||||||
}
|
}
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|||||||
Reference in New Issue
Block a user