diff --git a/lib/pages/space_management_v2/main_module/models/space_reorder_data_model.dart b/lib/pages/space_management_v2/main_module/models/space_reorder_data_model.dart new file mode 100644 index 00000000..d05f22c7 --- /dev/null +++ b/lib/pages/space_management_v2/main_module/models/space_reorder_data_model.dart @@ -0,0 +1,14 @@ +import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart'; + +class SpaceReorderDataModel { + const SpaceReorderDataModel({ + required this.space, + this.parent, + this.community, + }); + + final SpaceModel space; + final SpaceModel? parent; + final CommunityModel? community; +} diff --git a/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart b/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart index 4aea103a..3cf761ad 100644 --- a/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart @@ -1,13 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_connection_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/main_module/models/space_reorder_data_model.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/painters/spaces_connections_arrow_painter.dart'; +import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/create_space_button.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_card_widget.dart'; import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_cell.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; class CommunityStructureCanvas extends StatefulWidget { const CommunityStructureCanvas({ @@ -31,8 +35,9 @@ class _CommunityStructureCanvasState extends State final double _horizontalSpacing = 150.0; final double _verticalSpacing = 120.0; - late TransformationController _transformationController; - late AnimationController _animationController; + late final TransformationController _transformationController; + late final AnimationController _animationController; + SpaceReorderDataModel? _draggedData; @override void initState() { @@ -97,7 +102,7 @@ class _CommunityStructureCanvasState extends State final position = _positions[space.uuid]; if (position == null) return; - const scale = 1.5; + const scale = 1; final viewSize = context.size; if (viewSize == null) return; @@ -112,16 +117,33 @@ class _CommunityStructureCanvasState extends State _runAnimation(matrix); } + void _onReorder(SpaceReorderDataModel data, int newIndex) { + final newCommunity = widget.community.copyWith(); + final children = data.parent?.children ?? newCommunity.spaces; + final oldIndex = children.indexWhere((s) => s.uuid == data.space.uuid); + if (oldIndex != -1) { + final item = children.removeAt(oldIndex); + if (newIndex > oldIndex) { + children.insert(newIndex - 1, item); + } else { + children.insert(newIndex, item); + } + } + context.read().add( + CommunitiesUpdateCommunity(newCommunity), + ); + } + void _onSpaceTapped(SpaceModel? space) { context.read().add( SelectSpaceEvent(community: widget.community, space: space), ); } - void _resetSelectionAndZoom() { + void _resetSelectionAndZoom([CommunityModel? community]) { context.read().add( SelectSpaceEvent( - community: widget.community, + community: community ?? widget.community, space: null, ), ); @@ -182,7 +204,8 @@ class _CommunityStructureCanvasState extends State _positions.clear(); final community = widget.community; - _calculateLayout(community.spaces, 0, {}); + final levelXOffset = {}; + _calculateLayout(community.spaces, 0, levelXOffset); final selectedSpace = widget.selectedSpace; final highlightedUuids = {}; @@ -193,7 +216,24 @@ class _CommunityStructureCanvasState extends State final widgets = []; final connections = []; - _generateWidgets(community.spaces, widgets, connections, highlightedUuids); + _generateWidgets( + widget.community.spaces, + widgets, + connections, + highlightedUuids, + community: widget.community, + ); + + final createButtonX = levelXOffset[0] ?? 0.0; + const createButtonY = 0.0; + + widgets.add( + Positioned( + left: createButtonX, + top: createButtonY, + child: CreateSpaceButton(communityUuid: widget.community.uuid), + ), + ); return [ CustomPaint( @@ -211,58 +251,178 @@ class _CommunityStructureCanvasState extends State List spaces, List widgets, List connections, - Set highlightedUuids, - ) { - for (final space in spaces) { + Set highlightedUuids, { + CommunityModel? community, + SpaceModel? parent, + }) { + if (spaces.isNotEmpty) { + final firstChildPos = _positions[spaces.first.uuid]!; + final targetPos = Offset( + firstChildPos.dx - (_horizontalSpacing / 4), + firstChildPos.dy, + ); + widgets.add(_buildDropTarget(parent, community, 0, targetPos)); + } + + for (var i = 0; i < spaces.length; i++) { + final space = spaces[i]; final position = _positions[space.uuid]; - if (position == null) continue; + if (position == null) { + continue; + } final isHighlighted = highlightedUuids.contains(space.uuid); final hasNoSelectedSpace = widget.selectedSpace == null; + final spaceCard = SpaceCardWidget( + buildSpaceContainer: () { + return Opacity( + opacity: hasNoSelectedSpace || isHighlighted ? 1.0 : 0.5, + child: Tooltip( + message: space.spaceName, + preferBelow: false, + child: SpaceCell( + onTap: () => _onSpaceTapped(space), + icon: space.icon, + name: space.spaceName, + ), + ), + ); + }, + onTap: () => SpaceDetailsDialogHelper.showCreate( + context, + communityUuid: widget.community.uuid, + ), + ); + + final reorderData = SpaceReorderDataModel( + space: space, + parent: parent, + community: community, + ); + widgets.add( Positioned( left: position.dx, top: position.dy, width: _cardWidth, height: _cardHeight, - child: SpaceCardWidget( - buildSpaceContainer: () { - return Opacity( - opacity: hasNoSelectedSpace || isHighlighted ? 1.0 : 0.5, - child: Tooltip( - message: space.spaceName, - preferBelow: false, - child: SpaceCell( - onTap: () => _onSpaceTapped(space), - icon: space.icon, - name: space.spaceName, - ), + child: Draggable( + data: reorderData, + feedback: Material( + color: Colors.transparent, + child: Opacity( + opacity: 0.2, + child: SizedBox( + width: _cardWidth, + height: _cardHeight, + child: spaceCard, ), - ); - }, - onTap: () => SpaceDetailsDialogHelper.showCreate(context), + ), + ), + onDragStarted: () => setState(() => _draggedData = reorderData), + onDragEnd: (_) => setState(() => _draggedData = null), + onDraggableCanceled: (_, __) => setState(() => _draggedData = null), + childWhenDragging: Opacity(opacity: 0.4, child: spaceCard), + child: spaceCard, ), ), ); + final targetPos = Offset( + position.dx + _cardWidth + (_horizontalSpacing / 4) - 20, + position.dy, + ); + widgets.add(_buildDropTarget(parent, community, i + 1, targetPos)); + for (final child in space.children) { - connections.add( - SpaceConnectionModel(from: space.uuid, to: child.uuid), + connections.add(SpaceConnectionModel(from: space.uuid, to: child.uuid)); + } + + if (space.children.isNotEmpty) { + _generateWidgets( + space.children, + widgets, + connections, + highlightedUuids, + parent: space, ); } - _generateWidgets(space.children, widgets, connections, highlightedUuids); } } + Widget _buildDropTarget( + SpaceModel? parent, + CommunityModel? community, + int index, + Offset position, + ) { + return Positioned( + left: position.dx, + top: position.dy, + width: 40, + height: _cardHeight, + child: DragTarget( + builder: (context, candidateData, rejectedData) { + if (_draggedData == null) { + return const SizedBox(); + } + + final isTargetForDragged = (_draggedData?.parent?.uuid == parent?.uuid && + _draggedData?.community == null) || + (_draggedData?.community?.uuid == community?.uuid && + _draggedData?.parent == null); + + if (!isTargetForDragged) { + return const SizedBox(); + } + + return Container( + width: 40, + height: _cardHeight, + decoration: BoxDecoration( + color: context.theme.colorScheme.primary.withValues( + alpha: candidateData.isNotEmpty ? 0.7 : 0.3, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.add, + color: context.theme.colorScheme.onPrimary, + ), + ); + }, + onWillAcceptWithDetails: (data) { + final children = parent?.children ?? community?.spaces ?? []; + final isSameParent = (data.data.parent?.uuid == parent?.uuid && + data.data.community == null) || + (data.data.community?.uuid == community?.uuid && + data.data.parent == null); + + if (!isSameParent) { + return false; + } + + final oldIndex = + children.indexWhere((s) => s.uuid == data.data.space.uuid); + if (oldIndex == index || oldIndex == index - 1) { + return false; + } + return true; + }, + onAcceptWithDetails: (data) => _onReorder(data.data, index), + ), + ); + } + @override Widget build(BuildContext context) { final treeWidgets = _buildTreeWidgets(); return InteractiveViewer( transformationController: _transformationController, boundaryMargin: EdgeInsets.symmetric( - horizontal: MediaQuery.sizeOf(context).width * 0.3, - vertical: MediaQuery.sizeOf(context).height * 0.3, + horizontal: context.screenWidth * 0.3, + vertical: context.screenHeight * 0.3, ), minScale: 0.5, maxScale: 3.0, @@ -270,8 +430,8 @@ class _CommunityStructureCanvasState extends State child: GestureDetector( onTap: _resetSelectionAndZoom, child: SizedBox( - width: MediaQuery.sizeOf(context).width * 5, - height: MediaQuery.sizeOf(context).height * 5, + width: context.screenWidth * 5, + height: context.screenHeight * 5, child: Stack(children: treeWidgets), ), ), diff --git a/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart b/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart index 4f71075b..f27dc8b9 100644 --- a/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart +++ b/lib/pages/space_management_v2/main_module/widgets/community_structure_header.dart @@ -55,8 +55,9 @@ class CommunityStructureHeader extends StatelessWidget { children: [ Text( 'Community Structure', - style: theme.textTheme.headlineLarge - ?.copyWith(color: ColorsManager.blackColor), + style: theme.textTheme.headlineLarge?.copyWith( + color: ColorsManager.blackColor, + ), ), if (selectedCommunity != null) Row( @@ -67,8 +68,9 @@ class CommunityStructureHeader extends StatelessWidget { Flexible( child: SelectableText( selectedCommunity.name, - style: theme.textTheme.bodyLarge - ?.copyWith(color: ColorsManager.blackColor), + style: theme.textTheme.bodyLarge?.copyWith( + color: ColorsManager.blackColor, + ), maxLines: 1, ), ), @@ -93,12 +95,11 @@ class CommunityStructureHeader extends StatelessWidget { CommunityStructureHeaderActionButtons( onDelete: (space) {}, onDuplicate: (space) {}, - onEdit: (space) { - SpaceDetailsDialogHelper.showEdit( - context, - spaceModel: selectedSpace!, - ); - }, + onEdit: (space) => SpaceDetailsDialogHelper.showEdit( + context, + spaceModel: selectedSpace!, + communityUuid: selectedCommunity.uuid, + ), selectedSpace: selectedSpace, ), ], diff --git a/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart b/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart index 4cbfd7fd..90d359e2 100644 --- a/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart +++ b/lib/pages/space_management_v2/main_module/widgets/create_space_button.dart @@ -2,38 +2,66 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class CreateSpaceButton extends StatelessWidget { - const CreateSpaceButton({super.key}); +class CreateSpaceButton extends StatefulWidget { + const CreateSpaceButton({ + required this.communityUuid, + super.key, + }); + + final String communityUuid; + + @override + State createState() => _CreateSpaceButtonState(); +} + +class _CreateSpaceButtonState extends State { + bool _isHovered = false; @override Widget build(BuildContext context) { - return GestureDetector( - onTap: () => SpaceDetailsDialogHelper.showCreate(context), - child: Container( - height: 60, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: Colors.grey.withValues(alpha: 0.5), - spreadRadius: 5, - blurRadius: 7, - offset: const Offset(0, 3), - ), - ], + return Tooltip( + margin: const EdgeInsets.symmetric(vertical: 24), + message: 'Create a new space', + child: GestureDetector( + onTap: () => SpaceDetailsDialogHelper.showCreate( + context, + communityUuid: widget.communityUuid, ), - child: Center( - child: Container( - width: 40, - height: 40, - decoration: const BoxDecoration( - color: ColorsManager.boxColor, - shape: BoxShape.circle, - ), - child: const Icon( - Icons.add, - color: Colors.blue, + child: MouseRegion( + onEnter: (_) => setState(() => _isHovered = true), + onExit: (_) => setState(() => _isHovered = false), + child: AnimatedOpacity( + duration: const Duration(milliseconds: 100), + opacity: _isHovered ? 1.0 : 0.45, + child: Container( + width: 150, + height: 90, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.grey.withValues(alpha: 0.2), + spreadRadius: 3, + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Container( + margin: const EdgeInsets.symmetric(vertical: 20), + decoration: BoxDecoration( + border: Border.all(color: ColorsManager.borderColor, width: 2), + color: ColorsManager.boxColor, + shape: BoxShape.circle, + ), + child: const Center( + child: Icon( + Icons.add, + color: Colors.blue, + ), + ), + ), ), ), ), diff --git a/lib/pages/space_management_v2/main_module/widgets/space_card_widget.dart b/lib/pages/space_management_v2/main_module/widgets/space_card_widget.dart index e91e577f..54902280 100644 --- a/lib/pages/space_management_v2/main_module/widgets/space_card_widget.dart +++ b/lib/pages/space_management_v2/main_module/widgets/space_card_widget.dart @@ -22,22 +22,20 @@ class _SpaceCardWidgetState extends State { return MouseRegion( onEnter: (_) => setState(() => isHovered = true), onExit: (_) => setState(() => isHovered = false), - child: SizedBox( - child: Stack( - clipBehavior: Clip.none, - alignment: Alignment.center, - children: [ - widget.buildSpaceContainer(), - if (isHovered) - Positioned( - bottom: 0, - child: PlusButtonWidget( - offset: Offset.zero, - onButtonTap: widget.onTap, - ), + child: Stack( + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + widget.buildSpaceContainer(), + if (isHovered) + Positioned( + bottom: 0, + child: PlusButtonWidget( + offset: Offset.zero, + onButtonTap: widget.onTap, ), - ], - ), + ), + ], ), ); } diff --git a/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart b/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart index e1f1fc00..050eac87 100644 --- a/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart +++ b/lib/pages/space_management_v2/main_module/widgets/space_management_community_structure.dart @@ -13,11 +13,17 @@ class SpaceManagementCommunityStructure extends StatelessWidget { final selectionBloc = context.watch().state; final selectedCommunity = selectionBloc.selectedCommunity; final selectedSpace = selectionBloc.selectedSpace; - const spacer = Spacer(flex: 10); + const spacer = Spacer(flex: 6); return Visibility( visible: selectedCommunity!.spaces.isNotEmpty, - replacement: const Row( - children: [spacer, Expanded(child: CreateSpaceButton()), spacer], + replacement: Row( + children: [ + spacer, + Expanded( + child: CreateSpaceButton(communityUuid: selectedCommunity.uuid), + ), + spacer + ], ), child: Column( mainAxisSize: MainAxisSize.min, diff --git a/lib/pages/space_management_v2/modules/communities/presentation/widgets/communities_tree_failure_widget.dart b/lib/pages/space_management_v2/modules/communities/presentation/widgets/communities_tree_failure_widget.dart index cfd32f52..277347df 100644 --- a/lib/pages/space_management_v2/modules/communities/presentation/widgets/communities_tree_failure_widget.dart +++ b/lib/pages/space_management_v2/modules/communities/presentation/widgets/communities_tree_failure_widget.dart @@ -13,14 +13,14 @@ class CommunitiesTreeFailureWidget extends StatelessWidget { return Expanded( child: Center( child: Column( + spacing: 16, mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( + SelectableText( errorMessage ?? 'Something went wrong', textAlign: TextAlign.center, ), - const SizedBox(height: 16), - ElevatedButton( + FilledButton( onPressed: () => context.read().add( LoadCommunities( LoadCommunitiesParam( diff --git a/lib/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart b/lib/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart index b3e436b1..ec3c9f81 100644 --- a/lib/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart +++ b/lib/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart @@ -40,16 +40,6 @@ class SpaceDetailsModel extends Equatable { ); } - Map toJson() { - return { - 'uuid': uuid, - 'spaceName': spaceName, - 'icon': icon, - 'productAllocations': productAllocations.map((e) => e.toJson()).toList(), - 'subspaces': subspaces.map((e) => e.toJson()).toList(), - }; - } - SpaceDetailsModel copyWith({ String? uuid, String? spaceName, @@ -89,14 +79,6 @@ class ProductAllocation extends Equatable { ); } - Map toJson() { - return { - 'uuid': uuid, - 'product': product.toJson(), - 'tag': tag.toJson(), - }; - } - ProductAllocation copyWith({ String? uuid, Product? product, @@ -134,14 +116,6 @@ class Subspace extends Equatable { ); } - Map toJson() { - return { - 'uuid': uuid, - 'name': name, - 'productAllocations': productAllocations.map((e) => e.toJson()).toList(), - }; - } - Subspace copyWith({ String? uuid, String? name, diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart b/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart index de2c40f0..031e0399 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart @@ -2,23 +2,37 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_bloc.dart'; import 'package:syncrow_web/services/api/http_service.dart'; abstract final class SpaceDetailsDialogHelper { - static void showCreate(BuildContext context) { + static void showCreate( + BuildContext context, { + required String communityUuid, + }) { showDialog( context: context, - builder: (_) => BlocProvider( - create: (context) => SpaceDetailsBloc( - RemoteSpaceDetailsService(httpService: HTTPService()), - ), - child: SpaceDetailsDialog( - context: context, - title: const SelectableText('Create Space'), - spaceModel: SpaceModel.empty(), - onSave: (space) {}, + builder: (_) => MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => SpaceDetailsBloc( + RemoteSpaceDetailsService(httpService: HTTPService()), + ), + ), + ], + child: Builder( + builder: (context) => SpaceDetailsDialog( + context: context, + title: const SelectableText('Create Space'), + spaceModel: SpaceModel.empty(), + onSave: (space) {}, + communityUuid: communityUuid, + ), ), ), ); @@ -27,20 +41,82 @@ abstract final class SpaceDetailsDialogHelper { static void showEdit( BuildContext context, { required SpaceModel spaceModel, + required String communityUuid, }) { showDialog( context: context, - builder: (_) => BlocProvider( - create: (context) => SpaceDetailsBloc( - RemoteSpaceDetailsService(httpService: HTTPService()), - ), - child: SpaceDetailsDialog( - context: context, - title: const SelectableText('Edit Space'), - spaceModel: spaceModel, - onSave: (space) {}, + builder: (_) => MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => SpaceDetailsBloc( + RemoteSpaceDetailsService(httpService: HTTPService()), + ), + ), + BlocProvider( + create: (context) => UpdateSpaceBloc( + RemoteUpdateSpaceService(HTTPService()), + ), + ), + ], + child: Builder( + builder: (context) => BlocListener( + listener: _updateListener, + child: SpaceDetailsDialog( + context: context, + title: const SelectableText('Edit Space'), + spaceModel: spaceModel, + onSave: (space) => context.read().add( + UpdateSpace( + UpdateSpaceParam( + communityUuid: communityUuid, + space: space, + ), + ), + ), + communityUuid: communityUuid, + ), + ), ), ), ); } + + static void _updateListener(BuildContext context, UpdateSpaceState state) { + return switch (state) { + UpdateSpaceInitial() => null, + UpdateSpaceLoading() => _onLoading(context), + UpdateSpaceSuccess(:final space) => _onUpdateSuccess(context, space), + UpdateSpaceFailure(:final errorMessage) => _onError(context, errorMessage), + }; + } + + static void _onUpdateSuccess(BuildContext context, SpaceDetailsModel space) { + Navigator.of(context).pop(); + } + + static void _onLoading(BuildContext context) { + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => const Center(child: CircularProgressIndicator()), + ); + } + + static void _onError(BuildContext context, String errorMessage) { + Navigator.of(context).pop(); + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => AlertDialog( + title: const Text('Error'), + content: Text(errorMessage), + actions: [ + TextButton( + onPressed: Navigator.of(context).pop, + child: const Text('OK'), + ), + ], + ), + ); + } } diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart index ae772036..d97442ec 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart'; -import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart'; @@ -15,6 +14,7 @@ class SpaceDetailsDialog extends StatefulWidget { required this.spaceModel, required this.onSave, required this.context, + required this.communityUuid, super.key, }); @@ -22,6 +22,7 @@ class SpaceDetailsDialog extends StatefulWidget { final SpaceModel spaceModel; final void Function(SpaceDetailsModel space) onSave; final BuildContext context; + final String communityUuid; @override State createState() => _SpaceDetailsDialogState(); @@ -35,11 +36,7 @@ class _SpaceDetailsDialogState extends State { if (!isCreateMode) { final param = LoadSpaceDetailsParam( spaceUuid: widget.spaceModel.uuid, - communityUuid: widget.context - .read() - .state - .selectedCommunity! - .uuid, + communityUuid: widget.communityUuid, ); widget.context.read().add(LoadSpaceDetails(param)); } diff --git a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_dialog.dart b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_dialog.dart index 9e81c323..8faac548 100644 --- a/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_dialog.dart +++ b/lib/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_dialog.dart @@ -37,7 +37,7 @@ class _SpaceSubSpacesDialogState extends State { ..._subspaces, Subspace( name: name, - uuid: const Uuid().v4(), + uuid: '${const Uuid().v4()}-NewTag', productAllocations: const [], ), ]; diff --git a/lib/pages/space_management_v2/modules/tags/domain/models/tag.dart b/lib/pages/space_management_v2/modules/tags/domain/models/tag.dart index 370bdf47..c5bccdbb 100644 --- a/lib/pages/space_management_v2/modules/tags/domain/models/tag.dart +++ b/lib/pages/space_management_v2/modules/tags/domain/models/tag.dart @@ -3,41 +3,19 @@ import 'package:equatable/equatable.dart'; class Tag extends Equatable { final String uuid; final String name; - final String createdAt; - final String updatedAt; const Tag({ required this.uuid, required this.name, - required this.createdAt, - required this.updatedAt, }); - factory Tag.empty() => const Tag( - uuid: '', - name: '', - createdAt: '', - updatedAt: '', - ); - factory Tag.fromJson(Map json) { return Tag( uuid: json['uuid'] as String, name: json['name'] as String, - createdAt: json['createdAt'] as String, - updatedAt: json['updatedAt'] as String, ); } - Map toJson() { - return { - 'uuid': uuid, - 'name': name, - 'createdAt': createdAt, - 'updatedAt': updatedAt, - }; - } - @override - List get props => [uuid, name, createdAt, updatedAt]; + List get props => [uuid, name]; } diff --git a/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_dialog.dart b/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_dialog.dart index 3cab4abe..3f6d42ab 100644 --- a/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_dialog.dart +++ b/lib/pages/space_management_v2/modules/tags/presentation/widgets/assign_tags_dialog.dart @@ -214,9 +214,12 @@ class _AssignTagsDialogState extends State { for (final product in newProducts) { _space.productAllocations.add( ProductAllocation( - uuid: const Uuid().v4(), + uuid: '${const Uuid().v4()}-NewProductUuid', product: product, - tag: Tag.empty(), + tag: Tag( + uuid: '${const Uuid().v4()}-NewTag', + name: '', + ), ), ); } diff --git a/lib/pages/space_management_v2/modules/tags/presentation/widgets/product_tag_field.dart b/lib/pages/space_management_v2/modules/tags/presentation/widgets/product_tag_field.dart index 8bbf379d..30282123 100644 --- a/lib/pages/space_management_v2/modules/tags/presentation/widgets/product_tag_field.dart +++ b/lib/pages/space_management_v2/modules/tags/presentation/widgets/product_tag_field.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:uuid/uuid.dart'; class ProductTagField extends StatefulWidget { final List items; @@ -53,13 +54,8 @@ class _ProductTagFieldState extends State { void _submit(String value) { final lowerCaseValue = value.toLowerCase(); final selectedTag = widget.items.firstWhere( - (tag) => tag.name.toLowerCase() == lowerCaseValue, - orElse: () => Tag( - name: value, - uuid: '', - createdAt: '', - updatedAt: '', - ), + (e) => e.name.toLowerCase() == lowerCaseValue, + orElse: () => Tag(uuid: '${const Uuid().v4()}-NewTag', name: value), ); widget.onSelected(selectedTag); _closeDropdown(); diff --git a/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart b/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart index b15e6095..a70d3b85 100644 --- a/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart +++ b/lib/pages/space_management_v2/modules/update_space/data/services/remote_update_space_service.dart @@ -1,5 +1,7 @@ import 'package:dio/dio.dart'; +import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/services/update_space_service.dart'; import 'package:syncrow_web/services/api/api_exception.dart'; import 'package:syncrow_web/services/api/http_service.dart'; @@ -12,17 +14,23 @@ class RemoteUpdateSpaceService implements UpdateSpaceService { static const _defaultErrorMessage = 'Failed to update space'; @override - Future updateSpace(SpaceDetailsModel space) async { + Future updateSpace(UpdateSpaceParam param) async { try { - final response = await _httpService.put( - path: 'endpoint', - body: space.toJson(), - expectedResponseModel: (data) => SpaceDetailsModel.fromJson( - data as Map, - ), + final path = await _makeUrl(param); + await _httpService.put( + path: path, + body: param.toJson(), + expectedResponseModel: (data) { + final response = data as Map; + final isSuccess = response['success'] as bool; + if (!isSuccess) { + throw APIException(response['error'] as String); + } + return isSuccess; + }, ); - return response; + return param.space; } on DioException catch (e) { final message = e.response?.data as Map?; final error = message?['error'] as Map?; @@ -37,4 +45,23 @@ class RemoteUpdateSpaceService implements UpdateSpaceService { throw APIException(formattedErrorMessage); } } + + Future _makeUrl(UpdateSpaceParam param) async { + final projectUuid = await ProjectManager.getProjectUUID(); + if (projectUuid == null || projectUuid.isEmpty) { + throw APIException('Project UUID is not set'); + } + + final spaceUuid = param.space.uuid; + if (spaceUuid.isEmpty) { + throw APIException('Space UUID is not set'); + } + + final communityUuid = param.communityUuid; + if (communityUuid.isEmpty) { + throw APIException('Community UUID is not set'); + } + + return '/projects/$projectUuid/communities/$communityUuid/spaces/$spaceUuid'; + } } diff --git a/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart b/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart new file mode 100644 index 00000000..5dd9106d --- /dev/null +++ b/lib/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart @@ -0,0 +1,42 @@ +import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; + +class UpdateSpaceParam { + UpdateSpaceParam({ + required this.space, + required this.communityUuid, + }); + + final SpaceDetailsModel space; + final String communityUuid; + + Map toJson() { + return { + 'spaceName': space.spaceName, + 'icon': space.icon, + 'subspaces': space.subspaces.map((e) => e._toJson()).toList(), + 'productAllocations': + space.productAllocations.map((e) => e._toJson()).toList(), + }; + } +} + +extension _ProductAllocationToJson on ProductAllocation { + Map _toJson() { + final isNewTag = tag.uuid.isEmpty; + return { + if (isNewTag) 'tagName': tag.name else 'tagUuid': tag.uuid, + 'productUuid': product.uuid, + }; + } +} + +extension _SubspaceToJson on Subspace { + Map _toJson() { + final isNewSubspace = uuid.endsWith('-NewTag'); + return { + if (!isNewSubspace) 'uuid': uuid, + 'subspaceName': name, + 'productAllocations': productAllocations.map((e) => e._toJson()).toList(), + }; + } +} diff --git a/lib/pages/space_management_v2/modules/update_space/domain/services/update_space_service.dart b/lib/pages/space_management_v2/modules/update_space/domain/services/update_space_service.dart index 29bc9419..c75fc0d4 100644 --- a/lib/pages/space_management_v2/modules/update_space/domain/services/update_space_service.dart +++ b/lib/pages/space_management_v2/modules/update_space/domain/services/update_space_service.dart @@ -1,5 +1,6 @@ import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart'; -abstract class UpdateSpaceService { - Future updateSpace(SpaceDetailsModel space); +abstract interface class UpdateSpaceService { + Future updateSpace(UpdateSpaceParam param); } diff --git a/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_bloc.dart b/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_bloc.dart index 3bc4e187..0920b547 100644 --- a/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_bloc.dart +++ b/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_bloc.dart @@ -1,6 +1,7 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart'; +import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/params/update_space_param.dart'; import 'package:syncrow_web/pages/space_management_v2/modules/update_space/domain/services/update_space_service.dart'; import 'package:syncrow_web/services/api/api_exception.dart'; @@ -20,7 +21,7 @@ class UpdateSpaceBloc extends Bloc { ) async { emit(UpdateSpaceLoading()); try { - final updatedSpace = await _updateSpaceService.updateSpace(event.space); + final updatedSpace = await _updateSpaceService.updateSpace(event.param); emit(UpdateSpaceSuccess(updatedSpace)); } on APIException catch (e) { emit(UpdateSpaceFailure(e.message)); diff --git a/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_event.dart b/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_event.dart index b7d476af..ec08cdd2 100644 --- a/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_event.dart +++ b/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_event.dart @@ -8,10 +8,10 @@ sealed class UpdateSpaceEvent extends Equatable { } final class UpdateSpace extends UpdateSpaceEvent { - const UpdateSpace(this.space); + const UpdateSpace(this.param); - final SpaceDetailsModel space; + final UpdateSpaceParam param; @override - List get props => [space]; + List get props => [param]; } diff --git a/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_state.dart b/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_state.dart index f0bc5a2b..437cca60 100644 --- a/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_state.dart +++ b/lib/pages/space_management_v2/modules/update_space/presentation/bloc/update_space_state.dart @@ -21,10 +21,10 @@ final class UpdateSpaceSuccess extends UpdateSpaceState { } final class UpdateSpaceFailure extends UpdateSpaceState { - final String message; + final String errorMessage; - const UpdateSpaceFailure(this.message); + const UpdateSpaceFailure(this.errorMessage); @override - List get props => [message]; + List get props => [errorMessage]; }