mirror of
https://github.com/SyncrowIOT/web.git
synced 2026-10-26 08:27:57 +00:00
Compare commits
40
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
097e70b906 | ||
|
|
390f7288bd | ||
|
|
625b7b8304 | ||
|
|
d0b853b188 | ||
|
|
339a242e74 | ||
|
|
48c064c711 | ||
|
|
17e025b69f | ||
|
|
9b3e4a59af | ||
|
|
9f5e9af5fa | ||
|
|
6b79254a89 | ||
|
|
08f322165e | ||
|
|
1228e5e737 | ||
|
|
e7e0149b3a | ||
|
|
f8efc07fdb | ||
|
|
6ee650e9f8 | ||
|
|
a31eb27c92 | ||
|
|
0fda5457ae | ||
|
|
5bd257ee56 | ||
|
|
e44c3ae796 | ||
|
|
58b469b92a | ||
|
|
ae09cbda1e | ||
|
|
b59e7e4836 | ||
|
|
ed06b0ebd6 | ||
|
|
0bed2573a0 | ||
|
|
69092823a2 | ||
|
|
d2d5e76102 | ||
|
|
691beb2e86 | ||
|
|
819670867d | ||
|
|
1c256cc55c | ||
|
|
1d35377137 | ||
|
|
67ad986fcc | ||
|
|
90e5499f92 | ||
|
|
944b981ee0 | ||
|
|
e0ff139f30 | ||
|
|
e12252db96 | ||
|
|
80dea5c12d | ||
|
|
65ad9c5edf | ||
|
|
fa16eaf82f | ||
|
|
7935864208 | ||
|
|
edf8bdfdcd |
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/sos/bloc/sos_device_bloc.dart';
|
||||
|
||||
class SOSBatchControlView extends StatelessWidget {
|
||||
|
||||
@@ -4,17 +4,20 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_mod
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||
import 'package:syncrow_web/services/product_api.dart';
|
||||
import 'package:syncrow_web/services/space_mana_api.dart';
|
||||
import 'package:syncrow_web/services/space_model_mang_api.dart';
|
||||
|
||||
class SpaceManagementBloc
|
||||
extends Bloc<SpaceManagementEvent, SpaceManagementState> {
|
||||
final CommunitySpaceManagementApi _api;
|
||||
final ProductApi _productApi;
|
||||
final SpaceModelManagementApi _spaceModelApi;
|
||||
|
||||
List<ProductModel>? _cachedProducts;
|
||||
|
||||
SpaceManagementBloc(this._api, this._productApi)
|
||||
SpaceManagementBloc(this._api, this._productApi, this._spaceModelApi)
|
||||
: super(SpaceManagementInitial()) {
|
||||
on<LoadCommunityAndSpacesEvent>(_onLoadCommunityAndSpaces);
|
||||
on<UpdateSpacePositionEvent>(_onUpdateSpacePosition);
|
||||
@@ -26,6 +29,8 @@ class SpaceManagementBloc
|
||||
on<FetchProductsEvent>(_onFetchProducts);
|
||||
on<SelectSpaceEvent>(_onSelectSpace);
|
||||
on<NewCommunityEvent>(_onNewCommunity);
|
||||
on<BlankStateEvent>(_onBlankState);
|
||||
on<SpaceModelLoadEvent>(_onLoadSpaceModel);
|
||||
}
|
||||
|
||||
void _onUpdateCommunity(
|
||||
@@ -103,6 +108,45 @@ class SpaceManagementBloc
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onBlankState(
|
||||
BlankStateEvent event, Emitter<SpaceManagementState> emit) async {
|
||||
try {
|
||||
final previousState = state;
|
||||
|
||||
if (previousState is SpaceManagementLoaded ||
|
||||
previousState is BlankState) {
|
||||
final prevCommunities = (previousState as dynamic).communities ?? [];
|
||||
emit(BlankState(
|
||||
communities: List<CommunityModel>.from(prevCommunities),
|
||||
products: _cachedProducts ?? [],
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
final communities = await _api.fetchCommunities();
|
||||
final updatedCommunities =
|
||||
await Future.wait(communities.map((community) async {
|
||||
final spaces = await _fetchSpacesForCommunity(community.uuid);
|
||||
return CommunityModel(
|
||||
uuid: community.uuid,
|
||||
createdAt: community.createdAt,
|
||||
updatedAt: community.updatedAt,
|
||||
name: community.name,
|
||||
description: community.description,
|
||||
spaces: spaces,
|
||||
region: community.region,
|
||||
);
|
||||
}));
|
||||
|
||||
emit(BlankState(
|
||||
communities: updatedCommunities,
|
||||
products: _cachedProducts ?? [],
|
||||
));
|
||||
} catch (error) {
|
||||
emit(SpaceManagementError('Error loading communities: $error'));
|
||||
}
|
||||
}
|
||||
|
||||
void _onLoadCommunityAndSpaces(
|
||||
LoadCommunityAndSpacesEvent event,
|
||||
Emitter<SpaceManagementState> emit,
|
||||
@@ -195,11 +239,15 @@ class SpaceManagementBloc
|
||||
SelectCommunityEvent event,
|
||||
Emitter<SpaceManagementState> emit,
|
||||
) async {
|
||||
_handleCommunitySpaceStateUpdate(
|
||||
emit: emit,
|
||||
selectedCommunity: event.selectedCommunity,
|
||||
selectedSpace: null,
|
||||
);
|
||||
try {
|
||||
_handleCommunitySpaceStateUpdate(
|
||||
emit: emit,
|
||||
selectedCommunity: event.selectedCommunity,
|
||||
selectedSpace: null,
|
||||
);
|
||||
} catch (e) {
|
||||
emit(SpaceManagementError('Error updating state: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
void _onSelectSpace(
|
||||
@@ -223,7 +271,8 @@ class SpaceManagementBloc
|
||||
|
||||
try {
|
||||
if (previousState is SpaceManagementLoaded ||
|
||||
previousState is BlankState) {
|
||||
previousState is BlankState ||
|
||||
previousState is SpaceModelLoaded) {
|
||||
final communities = List<CommunityModel>.from(
|
||||
(previousState as dynamic).communities,
|
||||
);
|
||||
@@ -317,26 +366,26 @@ class SpaceManagementBloc
|
||||
try {
|
||||
if (space.uuid != null && space.uuid!.isNotEmpty) {
|
||||
final response = await _api.updateSpace(
|
||||
communityId: communityUuid,
|
||||
spaceId: space.uuid!,
|
||||
name: space.name,
|
||||
parentId: space.parent?.uuid,
|
||||
isPrivate: space.isPrivate,
|
||||
position: space.position,
|
||||
icon: space.icon,
|
||||
direction: space.incomingConnection?.direction,
|
||||
products: space.selectedProducts);
|
||||
communityId: communityUuid,
|
||||
spaceId: space.uuid!,
|
||||
name: space.name,
|
||||
parentId: space.parent?.uuid,
|
||||
isPrivate: space.isPrivate,
|
||||
position: space.position,
|
||||
icon: space.icon,
|
||||
direction: space.incomingConnection?.direction,
|
||||
);
|
||||
} else {
|
||||
// Call create if the space does not have a UUID
|
||||
final response = await _api.createSpace(
|
||||
communityId: communityUuid,
|
||||
name: space.name,
|
||||
parentId: space.parent?.uuid,
|
||||
isPrivate: space.isPrivate,
|
||||
position: space.position,
|
||||
icon: space.icon,
|
||||
direction: space.incomingConnection?.direction,
|
||||
products: space.selectedProducts);
|
||||
communityId: communityUuid,
|
||||
name: space.name,
|
||||
parentId: space.parent?.uuid,
|
||||
isPrivate: space.isPrivate,
|
||||
position: space.position,
|
||||
icon: space.icon,
|
||||
direction: space.incomingConnection?.direction,
|
||||
);
|
||||
space.uuid = response?.uuid;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -370,4 +419,37 @@ class SpaceManagementBloc
|
||||
}
|
||||
return result.toList(); // Convert back to a list
|
||||
}
|
||||
|
||||
void _onLoadSpaceModel(
|
||||
SpaceModelLoadEvent event, Emitter<SpaceManagementState> emit) async {
|
||||
emit(SpaceManagementLoading());
|
||||
try {
|
||||
List<CommunityModel> communities = await _api.fetchCommunities();
|
||||
|
||||
List<CommunityModel> updatedCommunities = await Future.wait(
|
||||
communities.map((community) async {
|
||||
List<SpaceModel> spaces =
|
||||
await _fetchSpacesForCommunity(community.uuid);
|
||||
return CommunityModel(
|
||||
uuid: community.uuid,
|
||||
createdAt: community.createdAt,
|
||||
updatedAt: community.updatedAt,
|
||||
name: community.name,
|
||||
description: community.description,
|
||||
spaces: spaces, // New spaces list
|
||||
region: community.region,
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
|
||||
List<SpaceTemplateModel> spaceModels =
|
||||
await _spaceModelApi.listSpaceModels(page: 1);
|
||||
emit(SpaceModelLoaded(
|
||||
communities: updatedCommunities,
|
||||
products: _cachedProducts ?? [],
|
||||
spaceModels: spaceModels));
|
||||
} catch (e) {
|
||||
emit(SpaceManagementError('Error loading communities and spaces: $e'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,3 +140,8 @@ class LoadSpaceHierarchyEvent extends SpaceManagementEvent {
|
||||
@override
|
||||
List<Object> get props => [communityId];
|
||||
}
|
||||
|
||||
|
||||
class BlankStateEvent extends SpaceManagementEvent {}
|
||||
|
||||
class SpaceModelLoadEvent extends SpaceManagementEvent {}
|
||||
|
||||
@@ -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/space_model/models/space_template_model.dart';
|
||||
|
||||
abstract class SpaceManagementState extends Equatable {
|
||||
const SpaceManagementState();
|
||||
@@ -27,6 +28,10 @@ class SpaceManagementLoaded extends SpaceManagementState {
|
||||
this.selectedSpace});
|
||||
}
|
||||
|
||||
class SpaceModelManagenetLoaded extends SpaceManagementState {
|
||||
SpaceModelManagenetLoaded();
|
||||
}
|
||||
|
||||
class BlankState extends SpaceManagementState {
|
||||
final List<CommunityModel> communities;
|
||||
final List<ProductModel> products;
|
||||
@@ -54,3 +59,18 @@ class SpaceManagementError extends SpaceManagementState {
|
||||
@override
|
||||
List<Object> get props => [errorMessage];
|
||||
}
|
||||
|
||||
class SpaceModelLoaded extends SpaceManagementState {
|
||||
final List<SpaceTemplateModel> spaceModels;
|
||||
final List<ProductModel> products;
|
||||
final List<CommunityModel> communities;
|
||||
|
||||
SpaceModelLoaded({
|
||||
required this.communities,
|
||||
required this.products,
|
||||
required this.spaceModels,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [communities, products, spaceModels];
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ class ProductModel {
|
||||
|
||||
// Factory method to create a Product from JSON
|
||||
factory ProductModel.fromMap(Map<String, dynamic> json) {
|
||||
String icon = _mapIconToProduct(json['prodType']);
|
||||
return ProductModel(
|
||||
uuid: json['uuid'],
|
||||
catName: json['catName'],
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||
|
||||
class SelectedProduct {
|
||||
final String productId;
|
||||
int count;
|
||||
final String productName;
|
||||
final ProductModel? product;
|
||||
|
||||
SelectedProduct({required this.productId, required this.count});
|
||||
SelectedProduct({required this.productId, required this.count, required this.productName, this.product});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'productId': productId,
|
||||
'count': count,
|
||||
'productName': productName,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ class SpaceModel {
|
||||
Offset position;
|
||||
bool isHovered;
|
||||
SpaceStatus status;
|
||||
List<SelectedProduct> selectedProducts;
|
||||
String internalId;
|
||||
|
||||
List<Connection> outgoingConnections = []; // Connections from this space
|
||||
@@ -41,7 +40,6 @@ class SpaceModel {
|
||||
this.isHovered = false,
|
||||
this.incomingConnection,
|
||||
this.status = SpaceStatus.unchanged,
|
||||
this.selectedProducts = const [],
|
||||
}) : internalId = internalId ?? const Uuid().v4();
|
||||
|
||||
factory SpaceModel.fromJson(Map<String, dynamic> json,
|
||||
@@ -85,14 +83,6 @@ class SpaceModel {
|
||||
icon: json['icon'] ?? Assets.location,
|
||||
position: Offset(json['x'] ?? 0, json['y'] ?? 0),
|
||||
isHovered: false,
|
||||
selectedProducts: json['spaceProducts'] != null
|
||||
? (json['spaceProducts'] as List).map((product) {
|
||||
return SelectedProduct(
|
||||
productId: product['product']['uuid'],
|
||||
count: product['productCount'],
|
||||
);
|
||||
}).toList()
|
||||
: [],
|
||||
);
|
||||
|
||||
if (json['incomingConnections'] != null &&
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
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';
|
||||
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_bloc.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/bloc/space_management_state.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/widgets/loaded_space_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/structure_selector/view/center_body_widget.dart';
|
||||
import 'package:syncrow_web/services/product_api.dart';
|
||||
import 'package:syncrow_web/services/space_mana_api.dart';
|
||||
import 'package:syncrow_web/services/space_model_mang_api.dart';
|
||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||
|
||||
class SpaceManagementPage extends StatefulWidget {
|
||||
@@ -22,48 +22,54 @@ class SpaceManagementPage extends StatefulWidget {
|
||||
class SpaceManagementPageState extends State<SpaceManagementPage> {
|
||||
final CommunitySpaceManagementApi _api = CommunitySpaceManagementApi();
|
||||
final ProductApi _productApi = ProductApi();
|
||||
Map<String, List<SpaceModel>> communitySpaces = {};
|
||||
List<ProductModel> products = [];
|
||||
bool isProductDataLoaded = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
final SpaceModelManagementApi _spaceModelApi = SpaceModelManagementApi();
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => SpaceManagementBloc(_api, _productApi)
|
||||
..add(LoadCommunityAndSpacesEvent()),
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (_) => SpaceManagementBloc(_api, _productApi, _spaceModelApi)
|
||||
..add(LoadCommunityAndSpacesEvent()),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (_) => CenterBodyBloc(),
|
||||
),
|
||||
],
|
||||
child: WebScaffold(
|
||||
appBarTitle: Text('Space Management',
|
||||
style: Theme.of(context).textTheme.headlineLarge),
|
||||
enableMenuSidebar: false,
|
||||
centerBody: CenterBodyWidget(),
|
||||
rightBody: const NavigateHomeGridView(),
|
||||
scaffoldBody: BlocBuilder<SpaceManagementBloc, SpaceManagementState>(
|
||||
builder: (context, state) {
|
||||
if (state is SpaceManagementLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is BlankState) {
|
||||
return LoadedSpaceView(
|
||||
communities: state.communities,
|
||||
selectedCommunity: null,
|
||||
selectedSpace: null,
|
||||
products: state.products,
|
||||
);
|
||||
} else if (state is SpaceManagementLoaded) {
|
||||
return LoadedSpaceView(
|
||||
communities: state.communities,
|
||||
selectedCommunity: state.selectedCommunity,
|
||||
selectedSpace: state.selectedSpace,
|
||||
products: state.products,
|
||||
);
|
||||
} else if (state is SpaceManagementError) {
|
||||
return Center(child: Text('Error: ${state.errorMessage}'));
|
||||
}
|
||||
return Container();
|
||||
}),
|
||||
builder: (context, state) {
|
||||
if (state is SpaceManagementLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is BlankState) {
|
||||
return LoadedSpaceView(
|
||||
communities: state.communities,
|
||||
selectedCommunity: null,
|
||||
selectedSpace: null,
|
||||
products: state.products,
|
||||
);
|
||||
} else if (state is SpaceManagementLoaded) {
|
||||
return LoadedSpaceView(
|
||||
communities: state.communities,
|
||||
selectedCommunity: state.selectedCommunity,
|
||||
selectedSpace: state.selectedSpace,
|
||||
products: state.products,
|
||||
);
|
||||
} else if (state is SpaceModelLoaded) {
|
||||
return LoadedSpaceView(
|
||||
communities: state.communities,
|
||||
products: state.products,
|
||||
spaceModels: state.spaceModels);
|
||||
} else if (state is SpaceManagementError) {
|
||||
return Center(child: Text('Error: ${state.errorMessage}'));
|
||||
}
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ class _AddDeviceWidgetState extends State<AddDeviceWidget> {
|
||||
_buildActionButton('Cancel', ColorsManager.boxColor, ColorsManager.blackColor, () {
|
||||
Navigator.of(context).pop();
|
||||
}),
|
||||
_buildActionButton('Continue', ColorsManager.secondaryColor, Colors.white, () {
|
||||
_buildActionButton('Continue', ColorsManager.secondaryColor, ColorsManager.whiteColors, () {
|
||||
Navigator.of(context).pop();
|
||||
if (widget.onProductsSelected != null) {
|
||||
widget.onProductsSelected!(productCounts);
|
||||
@@ -114,7 +114,7 @@ class _AddDeviceWidgetState extends State<AddDeviceWidget> {
|
||||
Widget _buildDeviceTypeTile(ProductModel product, Size size) {
|
||||
final selectedProduct = productCounts.firstWhere(
|
||||
(p) => p.productId == product.uuid,
|
||||
orElse: () => SelectedProduct(productId: product.uuid, count: 0),
|
||||
orElse: () => SelectedProduct(productId: product.uuid, count: 0, productName: product.catName, product: product),
|
||||
);
|
||||
|
||||
return SizedBox(
|
||||
@@ -143,7 +143,7 @@ class _AddDeviceWidgetState extends State<AddDeviceWidget> {
|
||||
if (newCount > 0) {
|
||||
if (!productCounts.contains(selectedProduct)) {
|
||||
productCounts
|
||||
.add(SelectedProduct(productId: product.uuid, count: newCount));
|
||||
.add(SelectedProduct(productId: product.uuid, count: newCount, productName: product.catName, product: product));
|
||||
} else {
|
||||
selectedProduct.count = newCount;
|
||||
}
|
||||
|
||||
+1
-1
@@ -178,7 +178,7 @@ class _CommunityStructureHeaderState extends State<CommunityStructureHeader> {
|
||||
padding: 2.0,
|
||||
height: buttonHeight,
|
||||
elevation: 0,
|
||||
borderColor: Colors.grey.shade300,
|
||||
borderColor: ColorsManager.lightGrayColor,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
||||
@@ -299,7 +299,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
isPrivate: false,
|
||||
children: [],
|
||||
status: SpaceStatus.newSpace,
|
||||
selectedProducts: selectedProducts);
|
||||
);
|
||||
|
||||
if (parentIndex != null && direction != null) {
|
||||
SpaceModel parentSpace = spaces[parentIndex];
|
||||
@@ -335,14 +335,12 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
icon: space.icon,
|
||||
editSpace: space,
|
||||
isEdit: true,
|
||||
selectedProducts: space.selectedProducts,
|
||||
onCreateSpace: (String name, String icon,
|
||||
List<SelectedProduct> selectedProducts) {
|
||||
setState(() {
|
||||
// Update the space's properties
|
||||
space.name = name;
|
||||
space.icon = icon;
|
||||
space.selectedProducts = selectedProducts;
|
||||
|
||||
if (space.status != SpaceStatus.newSpace) {
|
||||
space.status = SpaceStatus.modified; // Mark as modified
|
||||
|
||||
@@ -104,7 +104,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
width: screenWidth * 0.020,
|
||||
height: screenWidth * 0.020,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
color: ColorsManager.whiteColors,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
@@ -143,7 +143,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
}
|
||||
});
|
||||
},
|
||||
style: const TextStyle(color: Colors.black),
|
||||
style: const TextStyle(color: ColorsManager.blackColor),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Please enter the name',
|
||||
hintStyle: const TextStyle(
|
||||
@@ -212,7 +212,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
);
|
||||
},
|
||||
backgroundColor: ColorsManager.textFieldGreyColor,
|
||||
foregroundColor: Colors.black,
|
||||
foregroundColor: ColorsManager.blackColor,
|
||||
borderColor: ColorsManager.neutralGray,
|
||||
borderRadius: 16.0,
|
||||
padding: 10.0, // Reduced padding for smaller size
|
||||
|
||||
@@ -40,8 +40,8 @@ void showDeleteConfirmationDialog(BuildContext context, VoidCallback onConfirm,
|
||||
Navigator.of(context).pop(); // Close the first dialog
|
||||
showProcessingPopup(context, isSpace, onConfirm);
|
||||
},
|
||||
style: _dialogButtonStyle(Colors.blue),
|
||||
child: const Text('Continue', style: TextStyle(color: Colors.white)),
|
||||
style: _dialogButtonStyle(ColorsManager.spaceColor),
|
||||
child: const Text('Continue', style: TextStyle(color: ColorsManager.whiteColors)),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -83,7 +83,7 @@ void showProcessingPopup(BuildContext context, bool isSpace, VoidCallback onDele
|
||||
ElevatedButton(
|
||||
onPressed: onDelete,
|
||||
style: _dialogButtonStyle(ColorsManager.warningRed),
|
||||
child: const Text('Delete', style: TextStyle(color: Colors.white)),
|
||||
child: const Text('Delete', style: TextStyle(color: ColorsManager.whiteColors)),
|
||||
),
|
||||
CancelButton(
|
||||
label: 'Cancel',
|
||||
@@ -108,7 +108,7 @@ Widget _buildWarningIcon() {
|
||||
color: ColorsManager.warningRed,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.close, color: Colors.white, size: 40),
|
||||
child: const Icon(Icons.close, color: ColorsManager.whiteColors, size: 40),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class IconSelectionDialog extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2), // Shadow color
|
||||
color: ColorsManager.blackColor.withOpacity(0.2), // Shadow color
|
||||
blurRadius: 20, // Spread of the blur
|
||||
offset: const Offset(0, 8), // Offset of the shadow
|
||||
),
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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/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';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/view/space_model_page.dart';
|
||||
import 'package:syncrow_web/services/space_model_mang_api.dart';
|
||||
|
||||
class LoadedSpaceView extends StatefulWidget {
|
||||
final List<CommunityModel> communities;
|
||||
final CommunityModel? selectedCommunity;
|
||||
final SpaceModel? selectedSpace;
|
||||
final List<ProductModel>? products;
|
||||
final List<SpaceTemplateModel>? spaceModels;
|
||||
|
||||
const LoadedSpaceView({
|
||||
super.key,
|
||||
@@ -18,6 +24,7 @@ class LoadedSpaceView extends StatefulWidget {
|
||||
this.selectedCommunity,
|
||||
this.selectedSpace,
|
||||
this.products,
|
||||
this.spaceModels,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -27,6 +34,9 @@ class LoadedSpaceView extends StatefulWidget {
|
||||
class _LoadedStateViewState extends State<LoadedSpaceView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool hasSpaceModels =
|
||||
widget.spaceModels != null && widget.spaceModels!.isNotEmpty;
|
||||
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
@@ -38,13 +48,25 @@ class _LoadedStateViewState extends State<LoadedSpaceView> {
|
||||
widget.selectedCommunity?.uuid ??
|
||||
'',
|
||||
),
|
||||
CommunityStructureArea(
|
||||
selectedCommunity: widget.selectedCommunity,
|
||||
selectedSpace: widget.selectedSpace,
|
||||
spaces: widget.selectedCommunity?.spaces ?? [],
|
||||
products: widget.products,
|
||||
communities: widget.communities,
|
||||
),
|
||||
hasSpaceModels
|
||||
? Expanded(
|
||||
child: BlocProvider(
|
||||
create: (context) => SpaceModelBloc(
|
||||
api: SpaceModelManagementApi(),
|
||||
initialSpaceModels: widget.spaceModels ?? [],
|
||||
),
|
||||
child: SpaceModelPage(
|
||||
products: widget.products,
|
||||
),
|
||||
),
|
||||
)
|
||||
: CommunityStructureArea(
|
||||
selectedCommunity: widget.selectedCommunity,
|
||||
selectedSpace: widget.selectedSpace,
|
||||
spaces: widget.selectedCommunity?.spaces ?? [],
|
||||
products: widget.products,
|
||||
communities: widget.communities,
|
||||
),
|
||||
],
|
||||
),
|
||||
const GradientCanvasBorderWidget(),
|
||||
|
||||
@@ -45,7 +45,7 @@ class PlusButtonWidget extends StatelessWidget {
|
||||
color: ColorsManager.spaceColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.add, color: Colors.white, size: 20),
|
||||
child: const Icon(Icons.add, color: ColorsManager.whiteColors, size: 20),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -8,6 +8,8 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_m
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_tile.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_tile_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
@@ -118,7 +120,7 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
children: [
|
||||
Text('Communities',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: Colors.black,
|
||||
color: ColorsManager.blackColor,
|
||||
)),
|
||||
GestureDetector(
|
||||
onTap: () => _navigateToBlank(context),
|
||||
@@ -186,6 +188,8 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
_selectedSpaceUuid = null; // Update the selected community
|
||||
});
|
||||
|
||||
context.read<CenterBodyBloc>().add(CommunitySelectedEvent());
|
||||
|
||||
context.read<SpaceManagementBloc>().add(
|
||||
SelectCommunityEvent(selectedCommunity: community),
|
||||
);
|
||||
|
||||
@@ -78,7 +78,7 @@ class SpaceContainerWidget extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
color: ColorsManager.lightGrayColor.withOpacity(0.5),
|
||||
spreadRadius: 2,
|
||||
blurRadius: 5,
|
||||
offset: const Offset(0, 3), // Shadow position
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class SpaceWidget extends StatelessWidget {
|
||||
final String name;
|
||||
@@ -23,11 +24,11 @@ class SpaceWidget extends StatelessWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
color: ColorsManager.whiteColors,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
color: ColorsManager.lightGrayColor.withOpacity(0.5),
|
||||
spreadRadius: 5,
|
||||
blurRadius: 7,
|
||||
offset: const Offset(0, 3),
|
||||
@@ -36,7 +37,7 @@ class SpaceWidget extends StatelessWidget {
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.location_on, color: Colors.blue),
|
||||
const Icon(Icons.location_on, color: ColorsManager.spaceColor),
|
||||
const SizedBox(width: 8),
|
||||
Text(name, style: const TextStyle(fontSize: 16)),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
import 'package:flutter_bloc/flutter_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/tag_model.dart';
|
||||
|
||||
class AssignTagModelBloc
|
||||
extends Bloc<AssignTagModelEvent, AssignTagModelState> {
|
||||
AssignTagModelBloc() : super(AssignTagModelInitial()) {
|
||||
on<InitializeTagModels>((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;
|
||||
}
|
||||
}
|
||||
|
||||
final allTags = <TagModel>[];
|
||||
|
||||
for (var selectedProduct in event.addedProducts) {
|
||||
final existingCount = existingTagCounts[selectedProduct.productId] ?? 0;
|
||||
|
||||
if (selectedProduct.count == 0 ||
|
||||
selectedProduct.count <= existingCount) {
|
||||
allTags.addAll(initialTags
|
||||
.where((tag) => tag.product?.uuid == selectedProduct.productId));
|
||||
continue;
|
||||
}
|
||||
|
||||
final missingCount = selectedProduct.count - existingCount;
|
||||
|
||||
allTags.addAll(initialTags
|
||||
.where((tag) => tag.product?.uuid == selectedProduct.productId));
|
||||
|
||||
if (missingCount > 0) {
|
||||
allTags.addAll(List.generate(
|
||||
missingCount,
|
||||
(index) => TagModel(
|
||||
tag: '',
|
||||
product: selectedProduct.product,
|
||||
location: 'None',
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
emit(AssignTagModelLoaded(
|
||||
tags: allTags,
|
||||
isSaveEnabled: _validateTags(allTags),
|
||||
));
|
||||
});
|
||||
|
||||
on<UpdateTag>((event, emit) {
|
||||
final currentState = state;
|
||||
|
||||
if (currentState is AssignTagModelLoaded &&
|
||||
currentState.tags.isNotEmpty) {
|
||||
final tags = List<TagModel>.from(currentState.tags);
|
||||
tags[event.index].tag = event.tag;
|
||||
emit(AssignTagModelLoaded(
|
||||
tags: tags,
|
||||
isSaveEnabled: _validateTags(tags),
|
||||
errorMessage: _getValidationError(tags),
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
on<UpdateLocation>((event, emit) {
|
||||
final currentState = state;
|
||||
|
||||
if (currentState is AssignTagModelLoaded &&
|
||||
currentState.tags.isNotEmpty) {
|
||||
final tags = List<TagModel>.from(currentState.tags);
|
||||
|
||||
// Use copyWith for immutability
|
||||
tags[event.index] =
|
||||
tags[event.index].copyWith(location: event.location);
|
||||
|
||||
emit(AssignTagModelLoaded(
|
||||
tags: tags,
|
||||
isSaveEnabled: _validateTags(tags),
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
on<ValidateTagModels>((event, emit) {
|
||||
final currentState = state;
|
||||
|
||||
if (currentState is AssignTagModelLoaded &&
|
||||
currentState.tags.isNotEmpty) {
|
||||
final tags = List<TagModel>.from(currentState.tags);
|
||||
|
||||
emit(AssignTagModelLoaded(
|
||||
tags: tags,
|
||||
isSaveEnabled: _validateTags(tags),
|
||||
errorMessage: _getValidationError(tags),
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
on<DeleteTagModel>((event, emit) {
|
||||
final currentState = state;
|
||||
|
||||
if (currentState is AssignTagModelLoaded &&
|
||||
currentState.tags.isNotEmpty) {
|
||||
final updatedTags = List<TagModel>.from(currentState.tags)
|
||||
..remove(event.tagToDelete);
|
||||
|
||||
emit(AssignTagModelLoaded(
|
||||
tags: updatedTags,
|
||||
isSaveEnabled: _validateTags(updatedTags),
|
||||
));
|
||||
} else {
|
||||
emit(const AssignTagModelLoaded(
|
||||
tags: [],
|
||||
isSaveEnabled: false,
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool _validateTags(List<TagModel> tags) {
|
||||
if (tags.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet();
|
||||
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
|
||||
return uniqueTags.length == tags.length && !hasEmptyTag;
|
||||
}
|
||||
|
||||
String? _getValidationError(List<TagModel> tags) {
|
||||
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
|
||||
if (hasEmptyTag) return 'Tags cannot be empty.';
|
||||
final duplicateTags = tags
|
||||
.map((tag) => tag.tag?.trim() ?? '')
|
||||
.fold<Map<String, int>>({}, (map, tag) {
|
||||
map[tag] = (map[tag] ?? 0) + 1;
|
||||
return map;
|
||||
})
|
||||
.entries
|
||||
.where((entry) => entry.value > 1)
|
||||
.map((entry) => entry.key)
|
||||
.toList();
|
||||
|
||||
if (duplicateTags.isNotEmpty) {
|
||||
return 'Duplicate tags found: ${duplicateTags.join(', ')}';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
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/selected_product_model.dart';
|
||||
|
||||
abstract class AssignTagModelEvent extends Equatable {
|
||||
const AssignTagModelEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class InitializeTagModels extends AssignTagModelEvent {
|
||||
final List<TagModel>? initialTags;
|
||||
final List<SelectedProduct> addedProducts;
|
||||
|
||||
const InitializeTagModels({
|
||||
required this.initialTags,
|
||||
required this.addedProducts,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [initialTags ?? [], addedProducts];
|
||||
}
|
||||
|
||||
class UpdateTag extends AssignTagModelEvent {
|
||||
final int index;
|
||||
final String tag;
|
||||
|
||||
const UpdateTag({required this.index, required this.tag});
|
||||
|
||||
@override
|
||||
List<Object> get props => [index, tag];
|
||||
}
|
||||
|
||||
class UpdateLocation extends AssignTagModelEvent {
|
||||
final int index;
|
||||
final String location;
|
||||
|
||||
const UpdateLocation({required this.index, required this.location});
|
||||
|
||||
@override
|
||||
List<Object> get props => [index, location];
|
||||
}
|
||||
|
||||
class ValidateTagModels extends AssignTagModelEvent {}
|
||||
|
||||
class DeleteTagModel extends AssignTagModelEvent {
|
||||
final TagModel tagToDelete;
|
||||
final List<TagModel> tags;
|
||||
|
||||
const DeleteTagModel({required this.tagToDelete, required this.tags});
|
||||
|
||||
@override
|
||||
List<Object> get props => [tagToDelete, tags];
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
||||
|
||||
abstract class AssignTagModelState extends Equatable {
|
||||
const AssignTagModelState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class AssignTagModelInitial extends AssignTagModelState {}
|
||||
|
||||
class AssignTagModelLoading extends AssignTagModelState {}
|
||||
|
||||
class AssignTagModelLoaded extends AssignTagModelState {
|
||||
final List<TagModel> tags;
|
||||
final bool isSaveEnabled;
|
||||
final String? errorMessage;
|
||||
|
||||
const AssignTagModelLoaded({
|
||||
required this.tags,
|
||||
required this.isSaveEnabled,
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [tags, isSaveEnabled];
|
||||
}
|
||||
|
||||
class AssignTagModelError extends AssignTagModelState {
|
||||
final String errorMessage;
|
||||
|
||||
const AssignTagModelError(this.errorMessage);
|
||||
|
||||
@override
|
||||
List<Object> get props => [errorMessage];
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
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/selected_product_model.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/utils/color_manager.dart';
|
||||
|
||||
class AssignTagModelsDialog extends StatelessWidget {
|
||||
final List<ProductModel>? products;
|
||||
final List<SubspaceTemplateModel>? subspaces;
|
||||
final List<TagModel>? initialTags;
|
||||
final ValueChanged<List<TagModel>>? onTagsAssigned;
|
||||
final List<SelectedProduct> addedProducts;
|
||||
final List<String>? allTags;
|
||||
final String spaceName;
|
||||
final String title;
|
||||
|
||||
const AssignTagModelsDialog({
|
||||
Key? key,
|
||||
required this.products,
|
||||
required this.subspaces,
|
||||
required this.addedProducts,
|
||||
this.initialTags,
|
||||
this.onTagsAssigned,
|
||||
this.allTags,
|
||||
required this.spaceName,
|
||||
required this.title
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<String> locations =
|
||||
(subspaces ?? []).map((subspace) => subspace.subspaceName).toList();
|
||||
return BlocProvider(
|
||||
create: (_) => AssignTagModelBloc()
|
||||
..add(InitializeTagModels(
|
||||
initialTags: initialTags,
|
||||
addedProducts: addedProducts,
|
||||
)),
|
||||
child: BlocBuilder<AssignTagModelBloc, AssignTagModelState>(
|
||||
builder: (context, state) {
|
||||
if (state is AssignTagModelLoaded) {
|
||||
final controllers = List.generate(
|
||||
state.tags.length,
|
||||
(index) => TextEditingController(text: state.tags[index].tag),
|
||||
);
|
||||
|
||||
return AlertDialog(
|
||||
title: Text(title),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: DataTable(
|
||||
headingRowColor: WidgetStateProperty.all(
|
||||
ColorsManager.dataHeaderGrey),
|
||||
border: TableBorder.all(
|
||||
color: ColorsManager.dataHeaderGrey,
|
||||
width: 1,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
columns: [
|
||||
DataColumn(
|
||||
label: Text('#',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium)),
|
||||
DataColumn(
|
||||
label: Text('Device',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium)),
|
||||
DataColumn(
|
||||
label: Text('Tag',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium)),
|
||||
DataColumn(
|
||||
label: Text('Location',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium)),
|
||||
],
|
||||
rows: state.tags.isEmpty
|
||||
? [
|
||||
const DataRow(cells: [
|
||||
DataCell(
|
||||
Center(
|
||||
child: Text(
|
||||
'No Data Available',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: ColorsManager.lightGrayColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell(SizedBox()),
|
||||
DataCell(SizedBox()),
|
||||
DataCell(SizedBox()),
|
||||
])
|
||||
]
|
||||
: List.generate(state.tags.length, (index) {
|
||||
final tag = state.tags[index];
|
||||
final controller = controllers[index];
|
||||
|
||||
return DataRow(
|
||||
cells: [
|
||||
DataCell(Text(index.toString())),
|
||||
DataCell(
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
tag.product?.name ?? 'Unknown',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close,
|
||||
color: ColorsManager.warningRed,
|
||||
size: 16),
|
||||
onPressed: () {
|
||||
context
|
||||
.read<AssignTagModelBloc>()
|
||||
.add(DeleteTagModel(
|
||||
tagToDelete: tag,
|
||||
tags: state.tags));
|
||||
},
|
||||
tooltip: 'Delete Tag',
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: controller,
|
||||
onChanged: (value) {
|
||||
context
|
||||
.read<AssignTagModelBloc>()
|
||||
.add(UpdateTag(
|
||||
index: index,
|
||||
tag: value.trim(),
|
||||
));
|
||||
},
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Enter Tag',
|
||||
border: InputBorder.none,
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context)
|
||||
.size
|
||||
.width *
|
||||
0.15,
|
||||
child: PopupMenuButton<String>(
|
||||
color: ColorsManager.whiteColors,
|
||||
icon: const Icon(
|
||||
Icons.arrow_drop_down,
|
||||
color: ColorsManager.blackColor),
|
||||
onSelected: (value) {
|
||||
controller.text = value;
|
||||
context
|
||||
.read<AssignTagModelBloc>()
|
||||
.add(UpdateTag(
|
||||
index: index,
|
||||
tag: value,
|
||||
));
|
||||
},
|
||||
itemBuilder: (context) {
|
||||
return (allTags ?? [])
|
||||
.where((tagValue) => !state
|
||||
.tags
|
||||
.map((e) => e.tag)
|
||||
.contains(tagValue))
|
||||
.map((tagValue) {
|
||||
return PopupMenuItem<String>(
|
||||
textStyle: const TextStyle(
|
||||
color: ColorsManager
|
||||
.textPrimaryColor),
|
||||
value: tagValue,
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
BoxConstraints(
|
||||
minWidth: MediaQuery.of(
|
||||
context)
|
||||
.size
|
||||
.width *
|
||||
0.15,
|
||||
maxWidth: MediaQuery.of(
|
||||
context)
|
||||
.size
|
||||
.width *
|
||||
0.15,
|
||||
),
|
||||
child: Text(
|
||||
tagValue,
|
||||
overflow: TextOverflow
|
||||
.ellipsis,
|
||||
),
|
||||
));
|
||||
}).toList();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton<String>(
|
||||
value: tag.location ?? 'None',
|
||||
dropdownColor: ColorsManager
|
||||
.whiteColors, // Dropdown background
|
||||
style: const TextStyle(
|
||||
color: Colors
|
||||
.black), // Style for selected text
|
||||
items: [
|
||||
const DropdownMenuItem<String>(
|
||||
value: 'None',
|
||||
child: Text(
|
||||
'None',
|
||||
style: TextStyle(
|
||||
color: ColorsManager
|
||||
.textPrimaryColor),
|
||||
),
|
||||
),
|
||||
...locations.map((location) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: location,
|
||||
child: Text(
|
||||
location,
|
||||
style: const TextStyle(
|
||||
color: ColorsManager
|
||||
.textPrimaryColor),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
context
|
||||
.read<AssignTagModelBloc>()
|
||||
.add(UpdateLocation(
|
||||
index: index,
|
||||
location: value,
|
||||
));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
if (state.errorMessage != null)
|
||||
Text(
|
||||
state.errorMessage!,
|
||||
style: const TextStyle(color: ColorsManager.warningRed),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: CancelButton(
|
||||
label: 'Cancel',
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
await showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (context) => CreateSpaceModelDialog(
|
||||
products: products,
|
||||
allTags: allTags,
|
||||
spaceModel: SpaceTemplateModel(
|
||||
modelName: spaceName,
|
||||
subspaceModels: subspaces,
|
||||
tags: initialTags),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
borderRadius: 10,
|
||||
backgroundColor: state.isSaveEnabled
|
||||
? ColorsManager.secondaryColor
|
||||
: ColorsManager.grayColor,
|
||||
foregroundColor: ColorsManager.whiteColors,
|
||||
onPressed: state.isSaveEnabled
|
||||
? () async {
|
||||
Navigator.of(context).pop();
|
||||
final assignedTags = <TagModel>{};
|
||||
for (var tag in state.tags) {
|
||||
if (tag.location == null ||
|
||||
subspaces == null) {
|
||||
continue;
|
||||
}
|
||||
for (var subspace in subspaces!) {
|
||||
if (tag.location == subspace.subspaceName) {
|
||||
subspace.tags ??= [];
|
||||
subspace.tags!.add(tag);
|
||||
assignedTags.add(tag);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
state.tags.removeWhere(assignedTags.contains);
|
||||
await showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (context) => CreateSpaceModelDialog(
|
||||
products: products,
|
||||
allTags: allTags,
|
||||
spaceModel: SpaceTemplateModel(
|
||||
modelName: spaceName,
|
||||
subspaceModels: subspaces,
|
||||
tags: state.tags),
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
child: const Text('Save'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
} else if (state is AssignTagModelLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else {
|
||||
return const Center(child: Text('Something went wrong.'));
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_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/utils/constants/action_enum.dart';
|
||||
|
||||
class SubSpaceModelBloc extends Bloc<SubSpaceModelEvent, SubSpaceModelState> {
|
||||
SubSpaceModelBloc() : super(SubSpaceModelState([], [], '')) {
|
||||
// Handle AddSubSpaceModel Event
|
||||
on<AddSubSpaceModel>((event, emit) {
|
||||
// Check for duplicate names (case-insensitive)
|
||||
final existingNames =
|
||||
state.subSpaces.map((e) => e.subspaceName.toLowerCase()).toSet();
|
||||
|
||||
if (existingNames.contains(event.subSpace.subspaceName.toLowerCase())) {
|
||||
// Emit state with an error message if duplicate name exists
|
||||
emit(SubSpaceModelState(
|
||||
state.subSpaces,
|
||||
state.updatedSubSpaceModels,
|
||||
'Subspace name already exists.',
|
||||
));
|
||||
} else {
|
||||
// Add subspace if no duplicate exists
|
||||
final updatedSubSpaces =
|
||||
List<SubspaceTemplateModel>.from(state.subSpaces)
|
||||
..add(event.subSpace);
|
||||
|
||||
emit(SubSpaceModelState(
|
||||
updatedSubSpaces,
|
||||
state.updatedSubSpaceModels,
|
||||
'', // Clear error message
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
// Handle RemoveSubSpaceModel Event
|
||||
on<RemoveSubSpaceModel>((event, emit) {
|
||||
final updatedSubSpaces = List<SubspaceTemplateModel>.from(state.subSpaces)
|
||||
..remove(event.subSpace);
|
||||
|
||||
final updatedSubspaceModels = List<UpdateSubspaceTemplateModel>.from(
|
||||
state.updatedSubSpaceModels,
|
||||
);
|
||||
|
||||
if (event.subSpace.uuid?.isNotEmpty ?? false) {
|
||||
updatedSubspaceModels.add(UpdateSubspaceTemplateModel(
|
||||
action: Action.delete,
|
||||
uuid: event.subSpace.uuid!,
|
||||
));
|
||||
}
|
||||
|
||||
emit(SubSpaceModelState(
|
||||
updatedSubSpaces,
|
||||
updatedSubspaceModels,
|
||||
'', // Clear error message
|
||||
));
|
||||
});
|
||||
|
||||
// Handle UpdateSubSpaceModel Event
|
||||
on<UpdateSubSpaceModel>((event, emit) {
|
||||
final updatedSubSpaces = state.subSpaces.map((subSpace) {
|
||||
if (subSpace.uuid == event.updatedSubSpace.uuid) {
|
||||
return event.updatedSubSpace;
|
||||
}
|
||||
return subSpace;
|
||||
}).toList();
|
||||
|
||||
final updatedSubspaceModels = List<UpdateSubspaceTemplateModel>.from(
|
||||
state.updatedSubSpaceModels,
|
||||
);
|
||||
|
||||
updatedSubspaceModels.add(UpdateSubspaceTemplateModel(
|
||||
action: Action.update,
|
||||
uuid: event.updatedSubSpace.uuid!,
|
||||
));
|
||||
|
||||
emit(SubSpaceModelState(
|
||||
updatedSubSpaces,
|
||||
updatedSubspaceModels,
|
||||
'', // Clear error message
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||
|
||||
abstract class SubSpaceModelEvent {}
|
||||
|
||||
class AddSubSpaceModel extends SubSpaceModelEvent {
|
||||
final SubspaceTemplateModel subSpace;
|
||||
AddSubSpaceModel(this.subSpace);
|
||||
}
|
||||
|
||||
class RemoveSubSpaceModel extends SubSpaceModelEvent {
|
||||
final SubspaceTemplateModel subSpace;
|
||||
RemoveSubSpaceModel(this.subSpace);
|
||||
}
|
||||
|
||||
class UpdateSubSpaceModel extends SubSpaceModelEvent {
|
||||
final SubspaceTemplateModel updatedSubSpace;
|
||||
UpdateSubSpaceModel(this.updatedSubSpace);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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';
|
||||
|
||||
class SubSpaceModelState {
|
||||
final List<SubspaceTemplateModel> subSpaces;
|
||||
final List<UpdateSubspaceTemplateModel> updatedSubSpaceModels;
|
||||
final String errorMessage;
|
||||
|
||||
SubSpaceModelState(
|
||||
this.subSpaces,
|
||||
this.updatedSubSpaceModels,
|
||||
this.errorMessage,
|
||||
);
|
||||
|
||||
SubSpaceModelState copyWith({
|
||||
List<SubspaceTemplateModel>? subSpaces,
|
||||
List<UpdateSubspaceTemplateModel>? updatedSubSpaceModels,
|
||||
String? errorMessage,
|
||||
}) {
|
||||
return SubSpaceModelState(
|
||||
subSpaces ?? this.subSpaces,
|
||||
updatedSubSpaceModels ?? this.updatedSubSpaceModels,
|
||||
errorMessage ?? this.errorMessage,
|
||||
);
|
||||
}
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
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/create_subspace_model/bloc/subspace_model_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_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/utils/color_manager.dart';
|
||||
|
||||
class CreateSubSpaceModelDialog extends StatelessWidget {
|
||||
final bool isEdit;
|
||||
final String dialogTitle;
|
||||
final List<SubspaceTemplateModel>? existingSubSpaces;
|
||||
final String? spaceName;
|
||||
final List<TagModel>? spaceTagModels;
|
||||
final List<String>? allTags;
|
||||
final List<ProductModel>? products;
|
||||
final SpaceTemplateModel? spaceModel;
|
||||
|
||||
const CreateSubSpaceModelDialog({
|
||||
Key? key,
|
||||
required this.isEdit,
|
||||
required this.dialogTitle,
|
||||
this.existingSubSpaces,
|
||||
required this.allTags,
|
||||
required this.spaceName,
|
||||
required this.spaceTagModels,
|
||||
required this.products,
|
||||
required this.spaceModel,
|
||||
}) : 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 = SubSpaceModelBloc();
|
||||
if (existingSubSpaces != null) {
|
||||
for (var subSpace in existingSubSpaces!) {
|
||||
bloc.add(AddSubSpaceModel(subSpace));
|
||||
}
|
||||
}
|
||||
return bloc;
|
||||
},
|
||||
child: BlocBuilder<SubSpaceModelBloc, SubSpaceModelState>(
|
||||
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<SubSpaceModelBloc>()
|
||||
.add(RemoveSubSpaceModel(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<SubSpaceModelBloc>().add(
|
||||
AddSubSpaceModel(
|
||||
SubspaceTemplateModel(
|
||||
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 {
|
||||
Navigator.of(context).pop();
|
||||
await showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
CreateSpaceModelDialog(
|
||||
products: products,
|
||||
allTags: allTags,
|
||||
spaceModel: SpaceTemplateModel(
|
||||
modelName: spaceName ?? '',
|
||||
subspaceModels: existingSubSpaces,
|
||||
tags: spaceTagModels,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
onPressed: () async {
|
||||
final subSpaces = context
|
||||
.read<SubSpaceModelBloc>()
|
||||
.state
|
||||
.subSpaces;
|
||||
Navigator.of(context).pop();
|
||||
|
||||
await showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
CreateSpaceModelDialog(
|
||||
products: products,
|
||||
allTags: allTags,
|
||||
spaceModel: SpaceTemplateModel(
|
||||
modelName: spaceName ?? '',
|
||||
subspaceModels: subSpaces,
|
||||
tags: spaceTagModels,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
borderRadius: 10,
|
||||
foregroundColor: ColorsManager.whiteColors,
|
||||
child: const Text('OK'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import 'package:flutter_bloc/flutter_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';
|
||||
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/tag_model.dart';
|
||||
import 'package:syncrow_web/services/space_model_mang_api.dart';
|
||||
|
||||
class CreateSpaceModelBloc
|
||||
extends Bloc<CreateSpaceModelEvent, CreateSpaceModelState> {
|
||||
SpaceTemplateModel? _space;
|
||||
|
||||
final SpaceModelManagementApi _api;
|
||||
|
||||
CreateSpaceModelBloc(this._api) : super(CreateSpaceModelInitial()) {
|
||||
on<CreateSpaceTemplate>((event, emit) async {
|
||||
try {
|
||||
late SpaceTemplateModel spaceTemplate = event.spaceTemplate;
|
||||
|
||||
final tagBodyModels =
|
||||
spaceTemplate.tags?.map((tag) => tag.toTagBodyModel()).toList() ??
|
||||
[];
|
||||
|
||||
final subspaceTemplateBodyModels =
|
||||
spaceTemplate.subspaceModels?.map((subspaceModel) {
|
||||
final tagsubspaceBodyModels = subspaceModel.tags
|
||||
?.map((tag) => tag.toTagBodyModel())
|
||||
.toList() ??
|
||||
[];
|
||||
return CreateSubspaceTemplateModel()
|
||||
..subspaceName = subspaceModel.subspaceName
|
||||
..tags = tagsubspaceBodyModels;
|
||||
}).toList() ??
|
||||
[];
|
||||
final spaceModelBody = CreateSpaceTemplateBodyModel(
|
||||
modelName: spaceTemplate.modelName,
|
||||
tags: tagBodyModels,
|
||||
subspaceModels: subspaceTemplateBodyModels);
|
||||
|
||||
final newSpaceTemplate = await _api.createSpaceModel(spaceModelBody);
|
||||
spaceTemplate.uuid = newSpaceTemplate?.uuid ?? '';
|
||||
|
||||
if (newSpaceTemplate != null) {
|
||||
emit(CreateSpaceModelLoaded(spaceTemplate));
|
||||
|
||||
if (event.onCreate != null) {
|
||||
event.onCreate!(spaceTemplate);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
emit(CreateSpaceModelError('Error creating space model'));
|
||||
}
|
||||
});
|
||||
|
||||
on<LoadSpaceTemplate>((event, emit) {
|
||||
emit(CreateSpaceModelLoading());
|
||||
Future.delayed(const Duration(seconds: 1), () {
|
||||
if (_space != null) {
|
||||
emit(CreateSpaceModelLoaded(_space!));
|
||||
} else {
|
||||
emit(CreateSpaceModelError("No space template found"));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
on<UpdateSpaceTemplate>((event, emit) {
|
||||
_space = event.spaceTemplate;
|
||||
emit(CreateSpaceModelLoaded(_space!));
|
||||
});
|
||||
|
||||
on<AddSubspacesToSpaceTemplate>((event, emit) {
|
||||
final currentState = state;
|
||||
|
||||
if (currentState is CreateSpaceModelLoaded) {
|
||||
final updatedSpace = currentState.space.copyWith(
|
||||
subspaceModels: [
|
||||
...(_space!.subspaceModels ?? []),
|
||||
...event.subspaces,
|
||||
],
|
||||
);
|
||||
emit(CreateSpaceModelLoaded(updatedSpace));
|
||||
} else {
|
||||
emit(CreateSpaceModelError("Space template not initialized"));
|
||||
}
|
||||
});
|
||||
|
||||
on<UpdateSpaceTemplateName>((event, emit) {
|
||||
final currentState = state;
|
||||
if (currentState is CreateSpaceModelLoaded) {
|
||||
if (event.name.trim().isEmpty) {
|
||||
|
||||
emit(CreateSpaceModelLoaded(
|
||||
currentState.space,
|
||||
errorMessage: "Model name cannot be empty",
|
||||
));
|
||||
} else {
|
||||
final updatedSpaceModel =
|
||||
currentState.space.copyWith(modelName: event.name);
|
||||
|
||||
emit(CreateSpaceModelLoaded(updatedSpaceModel));
|
||||
}
|
||||
} else {
|
||||
emit(CreateSpaceModelError("Space template not initialized"));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import 'package:equatable/equatable.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';
|
||||
|
||||
abstract class CreateSpaceModelEvent extends Equatable {
|
||||
const CreateSpaceModelEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoadSpaceTemplate extends CreateSpaceModelEvent {}
|
||||
|
||||
class UpdateSpaceTemplate extends CreateSpaceModelEvent {
|
||||
final SpaceTemplateModel spaceTemplate;
|
||||
|
||||
UpdateSpaceTemplate(this.spaceTemplate);
|
||||
}
|
||||
|
||||
class CreateSpaceTemplate extends CreateSpaceModelEvent {
|
||||
final SpaceTemplateModel spaceTemplate;
|
||||
final Function(SpaceTemplateModel)? onCreate;
|
||||
|
||||
const CreateSpaceTemplate({
|
||||
required this.spaceTemplate,
|
||||
this.onCreate,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [spaceTemplate];
|
||||
}
|
||||
|
||||
class UpdateSpaceTemplateName extends CreateSpaceModelEvent {
|
||||
final String name;
|
||||
|
||||
UpdateSpaceTemplateName({required this.name});
|
||||
|
||||
@override
|
||||
List<Object> get props => [name];
|
||||
}
|
||||
|
||||
class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent {
|
||||
final List<SubspaceTemplateModel> subspaces;
|
||||
|
||||
AddSubspacesToSpaceTemplate(this.subspaces);
|
||||
}
|
||||
|
||||
class ValidateSpaceTemplateName extends CreateSpaceModelEvent {
|
||||
final String name;
|
||||
|
||||
ValidateSpaceTemplateName({required this.name});
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||
|
||||
abstract class CreateSpaceModelState extends Equatable {
|
||||
const CreateSpaceModelState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class CreateSpaceModelInitial extends CreateSpaceModelState {}
|
||||
|
||||
class CreateSpaceModelLoading extends CreateSpaceModelState {}
|
||||
|
||||
class CreateSpaceModelLoaded extends CreateSpaceModelState {
|
||||
final SpaceTemplateModel space;
|
||||
final String? errorMessage;
|
||||
|
||||
CreateSpaceModelLoaded(this.space, {this.errorMessage});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [space, errorMessage];
|
||||
}
|
||||
|
||||
class CreateSpaceModelError extends CreateSpaceModelState {
|
||||
final String message;
|
||||
|
||||
CreateSpaceModelError(this.message);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter_bloc/flutter_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/bloc/space_model_state.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||
import 'package:syncrow_web/services/space_model_mang_api.dart';
|
||||
|
||||
class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> {
|
||||
final SpaceModelManagementApi api;
|
||||
|
||||
SpaceModelBloc({
|
||||
required this.api,
|
||||
required List<SpaceTemplateModel> initialSpaceModels,
|
||||
}) : super(SpaceModelLoaded(spaceModels: initialSpaceModels)) {
|
||||
on<CreateSpaceModel>(_onCreateSpaceModel);
|
||||
}
|
||||
|
||||
Future<void> _onCreateSpaceModel(
|
||||
CreateSpaceModel event, Emitter<SpaceModelState> emit) async {
|
||||
final currentState = state;
|
||||
if (currentState is SpaceModelLoaded) {
|
||||
try {
|
||||
final newSpaceModel =
|
||||
await api.getSpaceModel(event.newSpaceModel.uuid ?? '');
|
||||
|
||||
if (newSpaceModel != null) {
|
||||
final updatedSpaceModels =
|
||||
List<SpaceTemplateModel>.from(currentState.spaceModels)
|
||||
..add(newSpaceModel);
|
||||
emit(SpaceModelLoaded(spaceModels: updatedSpaceModels));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(SpaceModelError(message: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||
|
||||
abstract class SpaceModelEvent extends Equatable {
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class LoadSpaceModels extends SpaceModelEvent {}
|
||||
|
||||
class CreateSpaceModel extends SpaceModelEvent {
|
||||
final SpaceTemplateModel newSpaceModel;
|
||||
|
||||
CreateSpaceModel({required this.newSpaceModel});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [newSpaceModel];
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||
|
||||
abstract class SpaceModelState extends Equatable {
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class SpaceModelInitial extends SpaceModelState {}
|
||||
|
||||
class SpaceModelLoading extends SpaceModelState {}
|
||||
|
||||
class SpaceModelLoaded extends SpaceModelState {
|
||||
final List<SpaceTemplateModel> spaceModels;
|
||||
|
||||
SpaceModelLoaded({required this.spaceModels});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [spaceModels];
|
||||
}
|
||||
|
||||
class SpaceModelError extends SpaceModelState {
|
||||
final String message;
|
||||
|
||||
SpaceModelError({required this.message});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
class TagBodyModel {
|
||||
late String uuid;
|
||||
late String tag;
|
||||
late final String? productUuid;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'uuid': uuid,
|
||||
'tag': tag,
|
||||
'productUuid': productUuid,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return toJson().toString();
|
||||
}
|
||||
}
|
||||
|
||||
class CreateSubspaceTemplateModel {
|
||||
late String subspaceName;
|
||||
late List<TagBodyModel>? tags;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'subspaceName': subspaceName,
|
||||
'tags': tags?.map((tag) => tag.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class CreateSpaceTemplateBodyModel {
|
||||
final String modelName;
|
||||
final List<dynamic>? tags;
|
||||
final List<dynamic>? subspaceModels;
|
||||
|
||||
CreateSpaceTemplateBodyModel({
|
||||
required this.modelName,
|
||||
this.tags,
|
||||
this.subspaceModels,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'modelName': modelName,
|
||||
'tags': tags,
|
||||
'subspaceModels': subspaceModels,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
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/space_model/models/subspace_template_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
||||
import 'package:syncrow_web/utils/constants/action_enum.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class SpaceTemplateModel extends Equatable {
|
||||
String? uuid;
|
||||
String modelName;
|
||||
List<SubspaceTemplateModel>? subspaceModels;
|
||||
final List<TagModel>? tags;
|
||||
String internalId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [modelName, subspaceModels];
|
||||
|
||||
SpaceTemplateModel({
|
||||
this.uuid,
|
||||
String? internalId,
|
||||
required this.modelName,
|
||||
this.subspaceModels,
|
||||
this.tags,
|
||||
}) : internalId = internalId ?? const Uuid().v4();
|
||||
|
||||
factory SpaceTemplateModel.fromJson(Map<String, dynamic> json) {
|
||||
final String internalId = json['internalId'] ?? const Uuid().v4();
|
||||
|
||||
return SpaceTemplateModel(
|
||||
uuid: json['uuid'] ?? '',
|
||||
internalId: internalId,
|
||||
modelName: json['modelName'] ?? '',
|
||||
subspaceModels: (json['subspaceModels'] as List<dynamic>?)
|
||||
?.where((e) => e is Map<String, dynamic>) // Validate type
|
||||
.map((e) =>
|
||||
SubspaceTemplateModel.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
[],
|
||||
tags: (json['tags'] as List<dynamic>?)
|
||||
?.where((item) => item is Map<String, dynamic>) // Validate type
|
||||
.map((item) => TagModel.fromJson(item as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
[],
|
||||
);
|
||||
}
|
||||
SpaceTemplateModel copyWith({
|
||||
String? uuid,
|
||||
String? modelName,
|
||||
List<SubspaceTemplateModel>? subspaceModels,
|
||||
List<TagModel>? tags,
|
||||
String? internalId,
|
||||
}) {
|
||||
return SpaceTemplateModel(
|
||||
uuid: uuid ?? this.uuid,
|
||||
modelName: modelName ?? this.modelName,
|
||||
subspaceModels: subspaceModels ?? this.subspaceModels,
|
||||
tags: tags ?? this.tags,
|
||||
internalId: internalId ?? this.internalId,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'uuid': uuid,
|
||||
'modelName': modelName,
|
||||
'subspaceModels': subspaceModels?.map((e) => e.toJson()).toList(),
|
||||
'tags': tags?.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class UpdateSubspaceTemplateModel {
|
||||
final String uuid;
|
||||
final Action action;
|
||||
final String? subspaceName;
|
||||
final List<UpdateTagModel>? tags;
|
||||
|
||||
UpdateSubspaceTemplateModel({
|
||||
required this.action,
|
||||
required this.uuid,
|
||||
this.subspaceName,
|
||||
this.tags,
|
||||
});
|
||||
|
||||
factory UpdateSubspaceTemplateModel.fromJson(Map<String, dynamic> json) {
|
||||
return UpdateSubspaceTemplateModel(
|
||||
action: ActionExtension.fromValue(json['action']),
|
||||
uuid: json['uuid'] ?? '',
|
||||
subspaceName: json['subspaceName'] ?? '',
|
||||
tags: (json['tags'] as List)
|
||||
.map((item) => UpdateTagModel.fromJson(item))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'action': action.value,
|
||||
'uuid': uuid,
|
||||
'subspaceName': subspaceName,
|
||||
'tags': tags?.map((e) => e.toJson()).toList() ?? [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class UpdateTagModel {
|
||||
final Action action;
|
||||
final String? uuid;
|
||||
final String tag;
|
||||
final bool disabled;
|
||||
final ProductModel? product;
|
||||
|
||||
UpdateTagModel({
|
||||
required this.action,
|
||||
this.uuid,
|
||||
required this.tag,
|
||||
required this.disabled,
|
||||
this.product,
|
||||
});
|
||||
|
||||
factory UpdateTagModel.fromJson(Map<String, dynamic> json) {
|
||||
return UpdateTagModel(
|
||||
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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
extension SpaceTemplateExtensions on SpaceTemplateModel {
|
||||
List<String> listAllTagValues() {
|
||||
final List<String> tagValues = [];
|
||||
|
||||
if (tags != null) {
|
||||
tagValues.addAll(
|
||||
tags!.map((tag) => tag.tag ?? '').where((tag) => tag.isNotEmpty));
|
||||
}
|
||||
|
||||
if (subspaceModels != null) {
|
||||
for (final subspace in subspaceModels!) {
|
||||
if (subspace.tags != null) {
|
||||
tagValues.addAll(
|
||||
subspace.tags!
|
||||
.map((tag) => tag.tag ?? '')
|
||||
.where((tag) => tag.isNotEmpty),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tagValues;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
||||
|
||||
class SubspaceTemplateModel {
|
||||
final String? uuid;
|
||||
String subspaceName;
|
||||
final bool disabled;
|
||||
List<TagModel>? tags;
|
||||
|
||||
SubspaceTemplateModel({
|
||||
this.uuid,
|
||||
required this.subspaceName,
|
||||
required this.disabled,
|
||||
this.tags,
|
||||
});
|
||||
|
||||
factory SubspaceTemplateModel.fromJson(Map<String, dynamic> json) {
|
||||
return SubspaceTemplateModel(
|
||||
uuid: json['uuid'] ?? '',
|
||||
subspaceName: json['subspaceName'] ?? '',
|
||||
disabled: json['disabled'] ?? false,
|
||||
tags: (json['tags'] as List<dynamic>?)
|
||||
?.map((item) => TagModel.fromJson(item))
|
||||
.toList() ??
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'uuid': uuid,
|
||||
'subspaceName': subspaceName,
|
||||
'disabled': disabled,
|
||||
'tags': tags?.map((e) => e.toJson()).toList() ?? [],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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 TagModel {
|
||||
String? uuid;
|
||||
String? tag;
|
||||
final ProductModel? product;
|
||||
String internalId;
|
||||
String? location;
|
||||
|
||||
TagModel(
|
||||
{this.uuid,
|
||||
required this.tag,
|
||||
this.product,
|
||||
String? internalId,
|
||||
this.location})
|
||||
: internalId = internalId ?? const Uuid().v4();
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
TagModel copyWith({
|
||||
String? tag,
|
||||
ProductModel? product,
|
||||
String? location,
|
||||
}) {
|
||||
return TagModel(
|
||||
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 TagModel {
|
||||
TagBodyModel toTagBodyModel() {
|
||||
return TagBodyModel()
|
||||
..uuid = uuid ?? ''
|
||||
..tag = tag ?? ''
|
||||
..productUuid = product?.uuid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
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/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/bloc/space_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/add_space_model_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class SpaceModelPage extends StatelessWidget {
|
||||
final List<ProductModel>? products;
|
||||
|
||||
const SpaceModelPage({Key? key, this.products}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SpaceModelBloc, SpaceModelState>(
|
||||
builder: (context, state) {
|
||||
if (state is SpaceModelLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is SpaceModelLoaded) {
|
||||
final spaceModels = state.spaceModels;
|
||||
final allTagValues = _getAllTagValues(spaceModels);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20.0, 16.0, 16.0, 16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: GridView.builder(
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
crossAxisSpacing: 10.0,
|
||||
mainAxisSpacing: 10.0,
|
||||
childAspectRatio: _calculateChildAspectRatio(context),
|
||||
),
|
||||
itemCount: spaceModels.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == spaceModels.length) {
|
||||
// Add Button
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext dialogContext) {
|
||||
return CreateSpaceModelDialog(
|
||||
products: products,
|
||||
allTags: allTagValues,
|
||||
onLoad: (newModel) {
|
||||
context.read<SpaceModelBloc>().add(
|
||||
CreateSpaceModel(
|
||||
newSpaceModel: newModel),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: const AddSpaceModelWidget(),
|
||||
);
|
||||
}
|
||||
// Render existing space model
|
||||
final model = spaceModels[index];
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(8.0),
|
||||
child: SpaceModelCardWidget(model: model),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
} else if (state is SpaceModelError) {
|
||||
return Center(
|
||||
child: Text(
|
||||
'Error: ${state.message}',
|
||||
style: const TextStyle(color: ColorsManager.warningRed),
|
||||
),
|
||||
);
|
||||
}
|
||||
return const Center(child: Text('Initializing...'));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
double _calculateChildAspectRatio(BuildContext context) {
|
||||
double screenWidth = MediaQuery.of(context).size.width;
|
||||
if (screenWidth > 1600) {
|
||||
return 2; // Taller cards for larger screens
|
||||
}
|
||||
if (screenWidth > 1200) {
|
||||
return 3; // Adjusted height for medium screens
|
||||
} else if (screenWidth > 800) {
|
||||
return 3.5; // Adjusted height for smaller screens
|
||||
} else {
|
||||
return 4.0; // Default ratio for smallest screens
|
||||
}
|
||||
}
|
||||
|
||||
List<String> _getAllTagValues(List<SpaceTemplateModel> spaceModels) {
|
||||
final List<String> allTags = [];
|
||||
for (final spaceModel in spaceModels) {
|
||||
if (spaceModel.tags != null) {
|
||||
allTags.addAll(spaceModel.listAllTagValues());
|
||||
}
|
||||
}
|
||||
return allTags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class AddSpaceModelWidget extends StatelessWidget {
|
||||
const AddSpaceModelWidget({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.whiteColors,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: ColorsManager.semiTransparentBlackColor,
|
||||
blurRadius: 15,
|
||||
offset: Offset(0, 4),
|
||||
spreadRadius: 0,
|
||||
),
|
||||
BoxShadow(
|
||||
color: ColorsManager.semiTransparentBlackColor,
|
||||
blurRadius: 25,
|
||||
offset: Offset(0, 15),
|
||||
spreadRadius: -5,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 60,
|
||||
height: 60, // Set a proper height here
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: ColorsManager.neutralGray,
|
||||
border: Border.all(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
width: 2.0,
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.add,
|
||||
size: 40,
|
||||
color: ColorsManager.spaceColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class ButtonContentWidget extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
|
||||
const ButtonContentWidget({
|
||||
Key? key,
|
||||
required this.icon,
|
||||
required this.label,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
return SizedBox(
|
||||
width: screenWidth * 0.25,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
border: Border.all(
|
||||
color: ColorsManager.neutralGray,
|
||||
width: 3.0,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
color: ColorsManager.spaceColor,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
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/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';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart';
|
||||
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart';
|
||||
import 'package:syncrow_web/services/space_model_mang_api.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class CreateSpaceModelDialog extends StatelessWidget {
|
||||
final List<ProductModel>? products;
|
||||
final List<String>? allTags;
|
||||
final SpaceTemplateModel? spaceModel;
|
||||
final void Function(SpaceTemplateModel newModel)? onLoad;
|
||||
|
||||
const CreateSpaceModelDialog({
|
||||
Key? key,
|
||||
this.products,
|
||||
this.allTags,
|
||||
this.spaceModel,
|
||||
this.onLoad,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final SpaceModelManagementApi _spaceModelApi = SpaceModelManagementApi();
|
||||
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final TextEditingController spaceNameController = TextEditingController(
|
||||
text: spaceModel?.modelName ?? '',
|
||||
);
|
||||
|
||||
return AlertDialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
content: SizedBox(
|
||||
width: screenWidth * 0.3,
|
||||
child: BlocProvider(
|
||||
create: (_) {
|
||||
final bloc = CreateSpaceModelBloc(_spaceModelApi);
|
||||
if (spaceModel != null) {
|
||||
bloc.add(UpdateSpaceTemplate(spaceModel!));
|
||||
} else {
|
||||
bloc.add(UpdateSpaceTemplate(SpaceTemplateModel(
|
||||
modelName: '',
|
||||
subspaceModels: const [],
|
||||
)));
|
||||
}
|
||||
|
||||
spaceNameController.addListener(() {
|
||||
bloc.add(UpdateSpaceTemplateName(name: spaceNameController.text));
|
||||
});
|
||||
|
||||
return bloc;
|
||||
},
|
||||
child: BlocBuilder<CreateSpaceModelBloc, CreateSpaceModelState>(
|
||||
builder: (context, state) {
|
||||
if (state is CreateSpaceModelLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is CreateSpaceModelLoaded) {
|
||||
final updatedSpaceModel = state.space;
|
||||
final subspaces = updatedSpaceModel.subspaceModels ?? [];
|
||||
final isNameValid = spaceNameController.text.trim().isNotEmpty;
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Create New Space Model',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineLarge
|
||||
?.copyWith(color: ColorsManager.blackColor),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
width: screenWidth * 0.25,
|
||||
child: TextField(
|
||||
controller: spaceNameController,
|
||||
onChanged: (value) {
|
||||
context
|
||||
.read<CreateSpaceModelBloc>()
|
||||
.add(UpdateSpaceTemplateName(name: value));
|
||||
},
|
||||
style: const TextStyle(color: ColorsManager.blackColor),
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: ColorsManager.textFieldGreyColor,
|
||||
hintText: 'Please enter the name',
|
||||
errorText: state.errorMessage,
|
||||
hintStyle: const TextStyle(
|
||||
color: ColorsManager.lightGrayColor),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 12.0,
|
||||
horizontal: 16.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SubspaceModelCreate(context,
|
||||
subspaces: state.space.subspaceModels ?? [],
|
||||
allTags: allTags,
|
||||
products: products,
|
||||
spaceModel: spaceModel,
|
||||
spaceTagModels: spaceModel?.tags ?? [],
|
||||
spaceNameController: spaceNameController),
|
||||
const SizedBox(height: 10),
|
||||
TagChipDisplay(context,
|
||||
screenWidth: screenWidth,
|
||||
spaceModel: updatedSpaceModel,
|
||||
products: products,
|
||||
subspaces: subspaces,
|
||||
allTags: allTags,
|
||||
spaceNameController: spaceNameController),
|
||||
const SizedBox(height: 20),
|
||||
SizedBox(
|
||||
width: screenWidth * 0.25,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CancelButton(
|
||||
label: 'Cancel',
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
onPressed: state.errorMessage == null ||
|
||||
isNameValid
|
||||
? () {
|
||||
final updatedSpaceTemplate =
|
||||
updatedSpaceModel.copyWith(
|
||||
modelName:
|
||||
spaceNameController.text.trim(),
|
||||
);
|
||||
context.read<CreateSpaceModelBloc>().add(
|
||||
CreateSpaceTemplate(
|
||||
spaceTemplate:
|
||||
updatedSpaceTemplate,
|
||||
onCreate: (newModel) {
|
||||
onLoad!(newModel);
|
||||
Navigator.of(context)
|
||||
.pop(); // Close the dialog
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
borderRadius: 10,
|
||||
foregroundColor: isNameValid
|
||||
? ColorsManager.whiteColors
|
||||
: ColorsManager.whiteColorsWithOpacity,
|
||||
child: const Text('OK'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else if (state is CreateSpaceModelError) {
|
||||
return Text(
|
||||
'Error: ${state.message}',
|
||||
style: const TextStyle(color: ColorsManager.warningRed),
|
||||
);
|
||||
}
|
||||
|
||||
return const Center(child: Text('Initializing...'));
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.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/subspace_chip_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class SpaceModelCardWidget extends StatelessWidget {
|
||||
final SpaceTemplateModel model;
|
||||
|
||||
const SpaceModelCardWidget({Key? key, required this.model}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Map<String, int> productTagCount = {};
|
||||
|
||||
if (model.tags != null) {
|
||||
for (var tag in model.tags!) {
|
||||
final prodIcon = tag.product?.icon ?? 'Unknown';
|
||||
productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (model.subspaceModels != null) {
|
||||
for (var subspace in model.subspaceModels!) {
|
||||
if (subspace.tags != null) {
|
||||
for (var tag in subspace.tags!) {
|
||||
final prodIcon = tag.product?.icon ?? 'Unknown';
|
||||
productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.whiteColors,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: ColorsManager.lightGrayColor.withOpacity(0.5),
|
||||
spreadRadius: 2,
|
||||
blurRadius: 5,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: const EdgeInsets.fromLTRB(16.0, 14.0, 8.0, 8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
model.modelName,
|
||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Expanded(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Wrap(
|
||||
spacing: 3.0,
|
||||
runSpacing: 3.0,
|
||||
children: [
|
||||
for (var subspace in model.subspaceModels!
|
||||
.take(calculateTakeCount(context)))
|
||||
SubspaceChipWidget(subspace: subspace.subspaceName),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (productTagCount.isNotEmpty)
|
||||
Container(
|
||||
width: 1,
|
||||
height: double.infinity,
|
||||
color: ColorsManager.softGray,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
),
|
||||
const SizedBox(width: 7),
|
||||
Expanded(
|
||||
child: Wrap(
|
||||
spacing: 4.0,
|
||||
runSpacing: 4.0,
|
||||
children: productTagCount.entries.map((entry) {
|
||||
final prodType = entry.key;
|
||||
final count = entry.value;
|
||||
|
||||
return Chip(
|
||||
label: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
prodType,
|
||||
width: 15,
|
||||
height: 16,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'x$count', // Product count
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: ColorsManager.textFieldGreyColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: const BorderSide(
|
||||
color: ColorsManager.transparentColor,
|
||||
width: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
int calculateTakeCount(BuildContext context) {
|
||||
double screenWidth = MediaQuery.of(context).size.width;
|
||||
// Adjust the count based on the screen width
|
||||
if (screenWidth > 1500) {
|
||||
return 3; // For large screens
|
||||
} else if (screenWidth > 1200) {
|
||||
return 2;
|
||||
} else {
|
||||
return 1; // For smaller screens
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class SubspaceChipWidget extends StatelessWidget {
|
||||
final String subspace;
|
||||
|
||||
const SubspaceChipWidget({
|
||||
Key? key,
|
||||
required this.subspace,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Chip(
|
||||
label: Text(
|
||||
subspace,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
backgroundColor: ColorsManager.textFieldGreyColor,
|
||||
labelStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(color: ColorsManager.spaceColor),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: const BorderSide(
|
||||
color: ColorsManager.transparentColor,
|
||||
width: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/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/button_content_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class SubspaceModelCreate extends StatelessWidget {
|
||||
final List<SubspaceTemplateModel> subspaces;
|
||||
final TextEditingController spaceNameController;
|
||||
final List<TagModel>? spaceTagModels;
|
||||
final List<String>? allTags;
|
||||
final List<ProductModel>? products;
|
||||
final SpaceTemplateModel? spaceModel;
|
||||
|
||||
const SubspaceModelCreate(
|
||||
BuildContext context, {
|
||||
Key? key,
|
||||
required this.subspaces,
|
||||
this.spaceTagModels,
|
||||
required this.allTags,
|
||||
required this.products,
|
||||
required this.spaceModel,
|
||||
required this.spaceNameController,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
return Container(
|
||||
child: subspaces.isEmpty
|
||||
? TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
overlayColor: ColorsManager.transparentColor,
|
||||
),
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
await showDialog<List<SubspaceTemplateModel>>(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return CreateSubSpaceModelDialog(
|
||||
allTags: allTags,
|
||||
spaceName: spaceNameController.text,
|
||||
spaceModel: spaceModel,
|
||||
spaceTagModels: spaceTagModels,
|
||||
products: products,
|
||||
isEdit: true,
|
||||
dialogTitle: subspaces.isEmpty
|
||||
? 'Create Sub-space'
|
||||
: 'Edit Sub-space',
|
||||
existingSubSpaces: subspaces,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: const ButtonContentWidget(
|
||||
icon: Icons.add,
|
||||
label: 'Create Sub Space',
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
width: screenWidth * 0.25,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
width: 3.0, // Border width
|
||||
),
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
...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 {
|
||||
Navigator.of(context).pop();
|
||||
await showDialog<List<SubspaceTemplateModel>>(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return CreateSubSpaceModelDialog(
|
||||
isEdit: true,
|
||||
dialogTitle: 'Edit Sub-space',
|
||||
existingSubSpaces: subspaces,
|
||||
allTags: allTags,
|
||||
spaceName: spaceNameController.text,
|
||||
spaceTagModels: spaceTagModels,
|
||||
products: products,
|
||||
spaceModel: spaceModel,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.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/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/button_content_widget.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';
|
||||
|
||||
class TagChipDisplay extends StatelessWidget {
|
||||
final double screenWidth;
|
||||
final SpaceTemplateModel? spaceModel;
|
||||
final List<ProductModel>? products;
|
||||
final List<SubspaceTemplateModel>? subspaces;
|
||||
final List<String>? allTags;
|
||||
final TextEditingController spaceNameController;
|
||||
|
||||
const TagChipDisplay(
|
||||
BuildContext context, {
|
||||
Key? key,
|
||||
required this.screenWidth,
|
||||
required this.spaceModel,
|
||||
required this.products,
|
||||
required this.subspaces,
|
||||
required this.allTags,
|
||||
required this.spaceNameController,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return (spaceModel?.tags?.isNotEmpty == true ||
|
||||
spaceModel?.subspaceModels
|
||||
?.any((subspace) => subspace.tags?.isNotEmpty == true) ==
|
||||
true)
|
||||
? SizedBox(
|
||||
width: screenWidth * 0.25,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
width: 3.0, // Border width
|
||||
),
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
// Combine tags from spaceModel and subspaces
|
||||
..._groupTags([
|
||||
...?spaceModel?.tags,
|
||||
...?spaceModel?.subspaceModels
|
||||
?.expand((subspace) => subspace.tags ?? [])
|
||||
]).entries.map(
|
||||
(entry) => Chip(
|
||||
avatar: SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: SvgPicture.asset(
|
||||
entry.key.icon ?? 'assets/icons/gateway.svg',
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
label: Text(
|
||||
'x${entry.value}', // Show count
|
||||
style: const TextStyle(
|
||||
color: ColorsManager.spaceColor,
|
||||
),
|
||||
),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: const BorderSide(
|
||||
color: ColorsManager.spaceColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
await showDialog<bool>(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (context) => AddDeviceTypeModelWidget(
|
||||
products: products,
|
||||
subspaces: subspaces,
|
||||
allTags: allTags,
|
||||
spaceName: spaceNameController.text,
|
||||
spaceTagModels: spaceModel?.tags,
|
||||
initialSelectedProducts:
|
||||
_createInitialSelectedProducts(
|
||||
spaceModel?.tags, spaceModel?.subspaceModels),
|
||||
),
|
||||
);
|
||||
// Edit action
|
||||
},
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
final result = await showDialog<bool>(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (context) => AddDeviceTypeModelWidget(
|
||||
products: products,
|
||||
subspaces: subspaces,
|
||||
allTags: allTags,
|
||||
spaceName: spaceNameController.text,
|
||||
),
|
||||
);
|
||||
if (result == true) {}
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
child: const ButtonContentWidget(
|
||||
icon: Icons.add,
|
||||
label: 'Add Devices',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Map<ProductModel, int> _groupTags(List<TagModel> tags) {
|
||||
final Map<ProductModel, int> groupedTags = {};
|
||||
for (var tag in tags) {
|
||||
if (tag.product != null) {
|
||||
groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
return groupedTags;
|
||||
}
|
||||
|
||||
List<SelectedProduct> _createInitialSelectedProducts(
|
||||
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces) {
|
||||
final Map<ProductModel, int> productCounts = {};
|
||||
|
||||
if (tags != null) {
|
||||
for (var tag in tags) {
|
||||
if (tag.product != null) {
|
||||
productCounts[tag.product!] = (productCounts[tag.product!] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (subspaces != null) {
|
||||
for (var subspace in subspaces) {
|
||||
if (subspace.tags != null) {
|
||||
for (var tag in subspace.tags!) {
|
||||
if (tag.product != null) {
|
||||
productCounts[tag.product!] =
|
||||
(productCounts[tag.product!] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return productCounts.entries
|
||||
.map((entry) => SelectedProduct(
|
||||
productId: entry.key.uuid,
|
||||
count: entry.value,
|
||||
productName: entry.key.name ?? 'Unnamed',
|
||||
product: entry.key,
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_state.dart';
|
||||
|
||||
// Bloc Implementation
|
||||
class CenterBodyBloc extends Bloc<CenterBodyEvent, CenterBodyState> {
|
||||
CenterBodyBloc() : super(InitialState()) {
|
||||
on<CommunityStructureSelectedEvent>((event, emit) {
|
||||
emit(CommunityStructureState());
|
||||
});
|
||||
|
||||
on<SpaceModelSelectedEvent>((event, emit) {
|
||||
emit(SpaceModelState());
|
||||
});
|
||||
|
||||
on<CommunitySelectedEvent>((event, emit) {
|
||||
emit(CommunitySelectedState());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Define Events
|
||||
abstract class CenterBodyEvent {}
|
||||
|
||||
class CommunityStructureSelectedEvent extends CenterBodyEvent {}
|
||||
|
||||
class SpaceModelSelectedEvent extends CenterBodyEvent {}
|
||||
|
||||
class CommunitySelectedEvent extends CenterBodyEvent {}
|
||||
@@ -0,0 +1,9 @@
|
||||
abstract class CenterBodyState {}
|
||||
|
||||
class InitialState extends CenterBodyState {}
|
||||
|
||||
class CommunityStructureState extends CenterBodyState {}
|
||||
|
||||
class SpaceModelState extends CenterBodyState {}
|
||||
|
||||
class CommunitySelectedState extends CenterBodyState {}
|
||||
@@ -0,0 +1,81 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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/structure_selector/bloc/center_body_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/structure_selector/bloc/center_body_state.dart';
|
||||
import '../bloc/center_body_bloc.dart';
|
||||
|
||||
class CenterBodyWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<CenterBodyBloc, CenterBodyState>(
|
||||
builder: (context, state) {
|
||||
if (state is InitialState) {
|
||||
context.read<CenterBodyBloc>().add(CommunityStructureSelectedEvent());
|
||||
}
|
||||
if (state is CommunityStructureState) {
|
||||
context.read<SpaceManagementBloc>().add(BlankStateEvent());
|
||||
}
|
||||
|
||||
if (state is SpaceModelState) {
|
||||
context.read<SpaceManagementBloc>().add(SpaceModelLoadEvent());
|
||||
}
|
||||
|
||||
return Container(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CenterBodyBloc>().add(CommunityStructureSelectedEvent());
|
||||
},
|
||||
child: Text(
|
||||
'Community Structure',
|
||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
fontWeight: state is CommunityStructureState || state is CommunitySelectedState
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
color: state is CommunityStructureState || state is CommunitySelectedState
|
||||
? Theme.of(context).textTheme.bodyLarge!.color
|
||||
: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.color!
|
||||
.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.read<CenterBodyBloc>().add(SpaceModelSelectedEvent());
|
||||
},
|
||||
child: Text(
|
||||
'Space Model',
|
||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
fontWeight: state is SpaceModelState
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
color: state is SpaceModelState
|
||||
? Theme.of(context).textTheme.bodyLarge!.color
|
||||
: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.color!
|
||||
.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart';
|
||||
|
||||
class AddDeviceTypeModelBloc
|
||||
extends Bloc<AddDeviceTypeModelEvent, List<SelectedProduct>> {
|
||||
AddDeviceTypeModelBloc(List<SelectedProduct> initialProducts)
|
||||
: super(initialProducts) {
|
||||
on<UpdateProductCountEvent>(_onUpdateProductCount);
|
||||
}
|
||||
|
||||
void _onUpdateProductCount(
|
||||
UpdateProductCountEvent event, Emitter<List<SelectedProduct>> emit) {
|
||||
final existingProduct = state.firstWhere(
|
||||
(p) => p.productId == event.productId,
|
||||
orElse: () => SelectedProduct(productId: event.productId, count: 0,productName: event.productName,product: event.product ),
|
||||
);
|
||||
|
||||
if (event.count > 0) {
|
||||
if (!state.contains(existingProduct)) {
|
||||
emit([
|
||||
...state,
|
||||
SelectedProduct(productId: event.productId, count: event.count, productName: event.productName, product: event.product)
|
||||
]);
|
||||
} else {
|
||||
final updatedList = state.map((p) {
|
||||
if (p.productId == event.productId) {
|
||||
return SelectedProduct(productId: p.productId, count: event.count, productName: p.productName,product: p.product);
|
||||
}
|
||||
return p;
|
||||
}).toList();
|
||||
emit(updatedList);
|
||||
}
|
||||
} else {
|
||||
emit(state.where((p) => p.productId != event.productId).toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||
|
||||
abstract class AddDeviceTypeModelEvent extends Equatable {
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class UpdateProductCountEvent extends AddDeviceTypeModelEvent {
|
||||
final String productId;
|
||||
final int count;
|
||||
final String productName;
|
||||
final ProductModel product;
|
||||
|
||||
UpdateProductCountEvent({required this.productId, required this.count, required this.productName, required this.product});
|
||||
|
||||
@override
|
||||
List<Object> get props => [productId, count];
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
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/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/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/widgets/action_button_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class AddDeviceTypeModelWidget extends StatelessWidget {
|
||||
final List<ProductModel>? products;
|
||||
final ValueChanged<List<SelectedProduct>>? onProductsSelected;
|
||||
final List<SelectedProduct>? initialSelectedProducts;
|
||||
final List<SubspaceTemplateModel>? subspaces;
|
||||
final List<TagModel>? spaceTagModels;
|
||||
final List<String>? allTags;
|
||||
final String spaceName;
|
||||
|
||||
const AddDeviceTypeModelWidget(
|
||||
{super.key,
|
||||
this.products,
|
||||
this.initialSelectedProducts,
|
||||
this.onProductsSelected,
|
||||
this.subspaces,
|
||||
this.allTags,
|
||||
this.spaceTagModels,
|
||||
required this.spaceName});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
final crossAxisCount = size.width > 1200
|
||||
? 8
|
||||
: size.width > 800
|
||||
? 5
|
||||
: 3;
|
||||
|
||||
return BlocProvider(
|
||||
create: (_) => AddDeviceTypeModelBloc(initialSelectedProducts ?? []),
|
||||
child: Builder(
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Add Devices'),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
content: SingleChildScrollView(
|
||||
child: Container(
|
||||
width: size.width * 0.9,
|
||||
height: size.height * 0.65,
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
child: ScrollableGridViewWidget(
|
||||
products: products, crossAxisCount: crossAxisCount),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
CancelButton(
|
||||
label: 'Cancel',
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
await showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (context) => CreateSpaceModelDialog(
|
||||
products: products,
|
||||
allTags: allTags,
|
||||
spaceModel: SpaceTemplateModel(
|
||||
modelName: spaceName,
|
||||
subspaceModels: subspaces,
|
||||
tags: spaceTagModels,
|
||||
)),
|
||||
);
|
||||
},
|
||||
),
|
||||
ActionButton(
|
||||
label: 'Continue',
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
foregroundColor: ColorsManager.whiteColors,
|
||||
onPressed: () async {
|
||||
final currentState =
|
||||
context.read<AddDeviceTypeModelBloc>().state;
|
||||
Navigator.of(context).pop();
|
||||
|
||||
if (currentState.isNotEmpty) {
|
||||
final initialTags = generateInitialTags(
|
||||
spaceTagModels: spaceTagModels,
|
||||
subspaces: subspaces,
|
||||
);
|
||||
|
||||
final dialogTitle = initialTags.isNotEmpty
|
||||
? 'Edit Device'
|
||||
: 'Assign Tags';
|
||||
await showDialog<bool>(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (context) => AssignTagModelsDialog(
|
||||
products: products,
|
||||
subspaces: subspaces,
|
||||
addedProducts: currentState,
|
||||
allTags: allTags,
|
||||
spaceName: spaceName,
|
||||
initialTags: initialTags,
|
||||
title: dialogTitle,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
List<TagModel> generateInitialTags({
|
||||
List<TagModel>? spaceTagModels,
|
||||
List<SubspaceTemplateModel>? subspaces,
|
||||
}) {
|
||||
final List<TagModel> initialTags = [];
|
||||
|
||||
if (spaceTagModels != null) {
|
||||
initialTags.addAll(spaceTagModels);
|
||||
}
|
||||
|
||||
if (subspaces != null) {
|
||||
for (var subspace in subspaces) {
|
||||
if (subspace.tags != null) {
|
||||
initialTags.addAll(
|
||||
subspace.tags!.map(
|
||||
(tag) => tag.copyWith(location: subspace.subspaceName),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return initialTags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
|
||||
class ActionButton extends StatelessWidget {
|
||||
final String label;
|
||||
final Color backgroundColor;
|
||||
final Color foregroundColor;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
const ActionButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.backgroundColor,
|
||||
required this.foregroundColor,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 120,
|
||||
child: DefaultButton(
|
||||
onPressed: onPressed,
|
||||
backgroundColor: backgroundColor,
|
||||
foregroundColor: foregroundColor,
|
||||
child: Text(label),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
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 DeviceIconWidget extends StatelessWidget {
|
||||
final String? icon;
|
||||
|
||||
const DeviceIconWidget({
|
||||
super.key,
|
||||
required this.icon,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 50,
|
||||
width: 50,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
border: Border.all(
|
||||
color: ColorsManager.neutralGray,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: SvgPicture.asset(
|
||||
icon ?? Assets.sensors,
|
||||
width: 30,
|
||||
height: 30,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class DeviceNameWidget extends StatelessWidget {
|
||||
final String? name;
|
||||
|
||||
const DeviceNameWidget({
|
||||
super.key,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 35,
|
||||
child: Text(
|
||||
name ?? '',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(color: ColorsManager.blackColor),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
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/selected_product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.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_type_model_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_icon_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_name_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class DeviceTypeTileWidget extends StatelessWidget {
|
||||
final ProductModel product;
|
||||
final List<SelectedProduct> productCounts;
|
||||
|
||||
const DeviceTypeTileWidget({
|
||||
super.key,
|
||||
required this.product,
|
||||
required this.productCounts,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedProduct = productCounts.firstWhere(
|
||||
(p) => p.productId == product.uuid,
|
||||
orElse: () => SelectedProduct(
|
||||
productId: product.uuid,
|
||||
count: 0,
|
||||
productName: product.catName,
|
||||
product: product),
|
||||
);
|
||||
|
||||
return Card(
|
||||
elevation: 2,
|
||||
color: ColorsManager.whiteColors,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
DeviceIconWidget(icon: product.icon ?? Assets.doorSensor),
|
||||
const SizedBox(height: 4),
|
||||
DeviceNameWidget(name: product.name),
|
||||
const SizedBox(height: 4),
|
||||
CounterWidget(
|
||||
initialCount: selectedProduct.count,
|
||||
onCountChanged: (newCount) {
|
||||
context.read<AddDeviceTypeModelBloc>().add(
|
||||
UpdateProductCountEvent(
|
||||
productId: product.uuid,
|
||||
count: newCount,
|
||||
productName: product.catName,
|
||||
product: product),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
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/selected_product_model.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/widgets/device_type_tile_widget.dart';
|
||||
|
||||
class ScrollableGridViewWidget extends StatelessWidget {
|
||||
final List<ProductModel>? products;
|
||||
final int crossAxisCount;
|
||||
final List<SelectedProduct>? initialProductCounts;
|
||||
|
||||
const ScrollableGridViewWidget({
|
||||
super.key,
|
||||
required this.products,
|
||||
required this.crossAxisCount,
|
||||
this.initialProductCounts,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scrollController = ScrollController();
|
||||
|
||||
return Scrollbar(
|
||||
controller: scrollController,
|
||||
thumbVisibility: true,
|
||||
child: BlocBuilder<AddDeviceTypeModelBloc, List<SelectedProduct>>(
|
||||
builder: (context, productCounts) {
|
||||
return GridView.builder(
|
||||
controller: scrollController,
|
||||
shrinkWrap: true,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: crossAxisCount,
|
||||
mainAxisSpacing: 6,
|
||||
crossAxisSpacing: 4,
|
||||
childAspectRatio: .8,
|
||||
),
|
||||
itemCount: products?.length ?? 0,
|
||||
itemBuilder: (context, index) {
|
||||
final product = products![index];
|
||||
final initialProductCount = _findInitialProductCount(product);
|
||||
|
||||
return DeviceTypeTileWidget(
|
||||
product: product,
|
||||
productCounts: initialProductCount != null
|
||||
? [...productCounts, initialProductCount]
|
||||
: productCounts,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
SelectedProduct? _findInitialProductCount(ProductModel product) {
|
||||
// Check if the product exists in initialProductCounts
|
||||
if (initialProductCounts == null) return null;
|
||||
final matchingProduct = initialProductCounts!.firstWhere(
|
||||
(selectedProduct) => selectedProduct.productId == product.uuid,
|
||||
orElse: () => SelectedProduct(
|
||||
productId: '',
|
||||
count: 0,
|
||||
productName: '',
|
||||
product: null,
|
||||
),
|
||||
);
|
||||
|
||||
// Check if the product was actually found
|
||||
return matchingProduct.productId.isNotEmpty ? matchingProduct : null;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_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_response_model.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
@@ -167,7 +166,6 @@ class CommunitySpaceManagementApi {
|
||||
bool isPrivate = false,
|
||||
required Offset position,
|
||||
String? icon,
|
||||
required List<SelectedProduct> products,
|
||||
}) async {
|
||||
try {
|
||||
final body = {
|
||||
@@ -177,7 +175,6 @@ class CommunitySpaceManagementApi {
|
||||
'y': position.dy,
|
||||
'direction': direction,
|
||||
'icon': icon,
|
||||
'products': products.map((product) => product.toJson()).toList(),
|
||||
};
|
||||
if (parentId != null) {
|
||||
body['parentUuid'] = parentId;
|
||||
@@ -207,7 +204,6 @@ class CommunitySpaceManagementApi {
|
||||
String? direction,
|
||||
bool isPrivate = false,
|
||||
required Offset position,
|
||||
required List<SelectedProduct> products,
|
||||
}) async {
|
||||
try {
|
||||
final body = {
|
||||
@@ -217,7 +213,6 @@ class CommunitySpaceManagementApi {
|
||||
'y': position.dy,
|
||||
'direction': direction,
|
||||
'icon': icon,
|
||||
'products': products.map((product) => product.toJson()).toList(),
|
||||
};
|
||||
if (parentId != null) {
|
||||
body['parentUuid'] = parentId;
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import 'package:flutter/material.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({int page = 1}) async {
|
||||
try {
|
||||
List<SpaceTemplateModel> spaceModels = [];
|
||||
bool hasNext = true;
|
||||
while (hasNext) {
|
||||
await HTTPService().get(
|
||||
path: ApiEndpoints.listSpaceModels
|
||||
.replaceAll('{projectId}', TempConst.projectId),
|
||||
queryParameters: {'page': page},
|
||||
expectedResponseModel: (json) {
|
||||
List<dynamic> jsonData = json['data'];
|
||||
hasNext = json['hasNext'] ?? false;
|
||||
int currentPage = json['page'] ?? 1;
|
||||
List<SpaceTemplateModel> spaceModelList = jsonData.map((jsonItem) {
|
||||
return SpaceTemplateModel.fromJson(jsonItem);
|
||||
}).toList();
|
||||
|
||||
spaceModels.addAll(spaceModelList);
|
||||
page = currentPage + 1;
|
||||
return spaceModelList;
|
||||
},
|
||||
);
|
||||
}
|
||||
return spaceModels;
|
||||
} catch (e) {
|
||||
debugPrint('Error fetching space models: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<SpaceTemplateModel?> createSpaceModel(
|
||||
CreateSpaceTemplateBodyModel spaceModel) async {
|
||||
try {
|
||||
final response = await HTTPService().post(
|
||||
path: ApiEndpoints.createSpaceModel
|
||||
.replaceAll('{projectId}', TempConst.projectId),
|
||||
showServerMessage: true,
|
||||
body: spaceModel.toJson(),
|
||||
expectedResponseModel: (json) {
|
||||
return SpaceTemplateModel.fromJson(json['data']);
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
debugPrint('Error creating space model: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<SpaceTemplateModel?> getSpaceModel(String spaceModelUuid) async {
|
||||
try {
|
||||
final response = await HTTPService().get(
|
||||
path: ApiEndpoints.getSpaceModel
|
||||
.replaceAll('{projectId}', TempConst.projectId)
|
||||
.replaceAll('{spaceModelUuid}', spaceModelUuid),
|
||||
showServerMessage: true,
|
||||
expectedResponseModel: (json) {
|
||||
debugPrint('Response JSON: $json');
|
||||
|
||||
return SpaceTemplateModel.fromJson(json['data']);
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
debugPrint('Error getting space model: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ abstract class ColorsManager {
|
||||
static Color primaryColorWithOpacity =
|
||||
const Color(0xFF023DFE).withOpacity(0.6);
|
||||
static const Color whiteColors = Colors.white;
|
||||
static Color whiteColorsWithOpacity = Colors.white.withOpacity(0.6);
|
||||
|
||||
static const Color secondaryColor = Color(0xFF023DFE);
|
||||
static const Color onSecondaryColor = Color(0xFF023DFE);
|
||||
static Color shadowBlackColor = Colors.black.withOpacity(0.2);
|
||||
@@ -54,6 +56,10 @@ abstract class ColorsManager {
|
||||
static const Color neutralGray = Color(0xFFE5E5E5);
|
||||
static const Color warningRed = Color(0xFFFF6465);
|
||||
static const Color borderColor = Color(0xFFE5E5E5);
|
||||
static const Color CircleImageBackground = Color(0xFFF4F4F4);
|
||||
static const Color softGray = Color(0xFFD5D5D5);
|
||||
static const Color semiTransparentBlack = Color(0x19000000);
|
||||
static const Color dataHeaderGrey = Color(0x33999999);
|
||||
static const Color circleImageBackground = Color(0xFFF4F4F4);
|
||||
static const Color circleRolesBackground = Color(0xFFF8F8F8);
|
||||
static const Color activeGreen = Color(0xFF99FF93);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
enum Action {
|
||||
update,
|
||||
add,
|
||||
delete,
|
||||
}
|
||||
|
||||
extension ActionExtension on Action {
|
||||
String get value {
|
||||
switch (this) {
|
||||
case Action.update:
|
||||
return 'update';
|
||||
case Action.add:
|
||||
return 'add';
|
||||
case Action.delete:
|
||||
return 'delete';
|
||||
}
|
||||
}
|
||||
|
||||
static Action fromValue(String value) {
|
||||
switch (value) {
|
||||
case 'update':
|
||||
return Action.update;
|
||||
case 'add':
|
||||
return Action.add;
|
||||
case 'delete':
|
||||
return Action.delete;
|
||||
default:
|
||||
throw ArgumentError('Invalid action: $value');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,12 @@ abstract class ApiEndpoints {
|
||||
static const String updateScene = '/scene/tap-to-run/{sceneId}';
|
||||
|
||||
static const String updateAutomation = '/automation/{automationId}';
|
||||
|
||||
//space model
|
||||
static const String listSpaceModels = '/projects/{projectId}/space-models';
|
||||
static const String createSpaceModel = '/projects/{projectId}/space-models';
|
||||
static const String getSpaceModel = '/projects/{projectId}/space-models/{spaceModelUuid}';
|
||||
|
||||
static const String roleTypes = '/role/types';
|
||||
static const String permission = '/permission/{roleUuid}';
|
||||
static const String inviteUser = '/invite-user';
|
||||
|
||||
Reference in New Issue
Block a user