mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 07:07:19 +00:00
Refactor Tags Service and Bloc for Improved Data Handling:
- Updated RemoteTagsService to remove LoadTagsParam and fetch project UUID internally, enhancing encapsulation and reducing parameter dependency. - Modified TagsService interface to reflect the new loading method signature. - Adjusted TagsBloc to align with the updated service method, simplifying the loading process. - Enhanced AssignTagsTable and AddDeviceTypeWidget to utilize the new data flow, improving maintainability and user experience.
This commit is contained in:
@ -1,10 +1,9 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/params/load_tags_param.dart';
|
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/services/tags_service.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/services/tags_service.dart';
|
||||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||||
import 'package:syncrow_web/services/api/http_service.dart';
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
|
||||||
|
|
||||||
final class RemoteTagsService implements TagsService {
|
final class RemoteTagsService implements TagsService {
|
||||||
const RemoteTagsService(this._httpService);
|
const RemoteTagsService(this._httpService);
|
||||||
@ -14,17 +13,10 @@ final class RemoteTagsService implements TagsService {
|
|||||||
static const _defaultErrorMessage = 'Failed to load tags';
|
static const _defaultErrorMessage = 'Failed to load tags';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Tag>> loadTags(LoadTagsParam param) async {
|
Future<List<Tag>> loadTags() async {
|
||||||
if (param.projectUuid == null) {
|
|
||||||
throw Exception('Project UUID is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final response = await _httpService.get(
|
final response = await _httpService.get(
|
||||||
path: ApiEndpoints.listTags.replaceAll(
|
path: await _makeUrl(),
|
||||||
'{projectUuid}',
|
|
||||||
param.projectUuid!,
|
|
||||||
),
|
|
||||||
expectedResponseModel: (json) {
|
expectedResponseModel: (json) {
|
||||||
final result = json as Map<String, dynamic>;
|
final result = json as Map<String, dynamic>;
|
||||||
final data = result['data'] as List<dynamic>;
|
final data = result['data'] as List<dynamic>;
|
||||||
@ -46,4 +38,12 @@ final class RemoteTagsService implements TagsService {
|
|||||||
throw APIException(formattedErrorMessage);
|
throw APIException(formattedErrorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> _makeUrl() async {
|
||||||
|
final projectUuid = await ProjectManager.getProjectUUID();
|
||||||
|
if (projectUuid == null || projectUuid.isEmpty) {
|
||||||
|
throw APIException('Project UUID is required');
|
||||||
|
}
|
||||||
|
return '/projects/$projectUuid/tags';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/params/load_tags_param.dart';
|
|
||||||
|
|
||||||
abstract interface class TagsService {
|
abstract interface class TagsService {
|
||||||
Future<List<Tag>> loadTags(LoadTagsParam param);
|
Future<List<Tag>> loadTags();
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/params/load_tags_param.dart';
|
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/services/tags_service.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/services/tags_service.dart';
|
||||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||||
|
|
||||||
@ -21,7 +20,7 @@ class TagsBloc extends Bloc<TagsEvent, TagsState> {
|
|||||||
) async {
|
) async {
|
||||||
emit(TagsLoading());
|
emit(TagsLoading());
|
||||||
try {
|
try {
|
||||||
final tags = await _tagsService.loadTags(event.param);
|
final tags = await _tagsService.loadTags();
|
||||||
emit(TagsLoaded(tags));
|
emit(TagsLoaded(tags));
|
||||||
} on APIException catch (e) {
|
} on APIException catch (e) {
|
||||||
emit(TagsFailure(e.message));
|
emit(TagsFailure(e.message));
|
||||||
|
@ -8,10 +8,5 @@ abstract class TagsEvent extends Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LoadTags extends TagsEvent {
|
class LoadTags extends TagsEvent {
|
||||||
final LoadTagsParam param;
|
const LoadTags();
|
||||||
|
|
||||||
const LoadTags(this.param);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [param];
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/data/services/remote_products_service.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/products/data/services/remote_products_service.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/presentation/bloc/products_bloc.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/products/presentation/bloc/products_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_action_buttons.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/presentation/widgets/products_grid.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/tags/presentation/widgets/products_grid.dart';
|
||||||
import 'package:syncrow_web/services/api/http_service.dart';
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
@ -32,6 +33,13 @@ class AddDeviceTypeWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
actions: [
|
||||||
|
SpaceDetailsActionButtons(
|
||||||
|
onSave: () {},
|
||||||
|
onCancel: Navigator.of(context).pop,
|
||||||
|
saveButtonLabel: 'Next',
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/common/dialog_dropdown.dart';
|
import 'package:syncrow_web/common/dialog_dropdown.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/tags/data/services/remote_tags_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/tags/presentation/bloc/tags_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/presentation/widgets/product_tag_field.dart';
|
import 'package:syncrow_web/pages/space_management_v2/modules/tags/presentation/widgets/product_tag_field.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
@ -48,123 +51,138 @@ class _AssignTagsTableState extends State<AssignTagsTable> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ClipRRect(
|
return BlocProvider<TagsBloc>(
|
||||||
borderRadius: BorderRadius.circular(20),
|
create: (BuildContext context) => TagsBloc(
|
||||||
child: DataTable(
|
RemoteTagsService(HTTPService()),
|
||||||
headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey),
|
)..add(const LoadTags()),
|
||||||
key: ValueKey(widget.productAllocations.length),
|
child: BlocBuilder<TagsBloc, TagsState>(
|
||||||
border: TableBorder.all(
|
builder: (context, state) {
|
||||||
color: ColorsManager.dataHeaderGrey,
|
return switch (state) {
|
||||||
width: 1,
|
TagsLoading() || TagsInitial() => const Center(
|
||||||
borderRadius: BorderRadius.circular(20),
|
child: CircularProgressIndicator(),
|
||||||
),
|
),
|
||||||
columns: [
|
TagsFailure(:final message) => Center(
|
||||||
_buildDataColumn('#'),
|
child: Text(message),
|
||||||
_buildDataColumn('Device'),
|
),
|
||||||
_buildDataColumn('Tag'),
|
TagsLoaded(:final tags) => ClipRRect(
|
||||||
_buildDataColumn('Location'),
|
borderRadius: BorderRadius.circular(20),
|
||||||
],
|
child: DataTable(
|
||||||
rows: widget.productAllocations.isEmpty
|
headingRowColor: WidgetStateProperty.all(
|
||||||
? [
|
ColorsManager.dataHeaderGrey,
|
||||||
DataRow(
|
),
|
||||||
cells: [
|
key: ValueKey(widget.productAllocations.length),
|
||||||
DataCell(
|
border: TableBorder.all(
|
||||||
Center(
|
color: ColorsManager.dataHeaderGrey,
|
||||||
child: SelectableText(
|
width: 1,
|
||||||
'No Devices Available',
|
borderRadius: BorderRadius.circular(20),
|
||||||
style: context.textTheme.bodyMedium?.copyWith(
|
),
|
||||||
color: ColorsManager.lightGrayColor,
|
columns: [
|
||||||
),
|
_buildDataColumn('#'),
|
||||||
),
|
_buildDataColumn('Device'),
|
||||||
),
|
_buildDataColumn('Tag'),
|
||||||
),
|
_buildDataColumn('Location'),
|
||||||
DataCell.empty,
|
|
||||||
DataCell.empty,
|
|
||||||
DataCell.empty,
|
|
||||||
],
|
],
|
||||||
),
|
rows: widget.productAllocations.isEmpty
|
||||||
]
|
? [
|
||||||
: List.generate(widget.productAllocations.length, (index) {
|
DataRow(
|
||||||
final productAllocation = widget.productAllocations[index];
|
cells: [
|
||||||
final controller = _controllers[index];
|
DataCell(
|
||||||
|
Center(
|
||||||
|
child: SelectableText(
|
||||||
|
'No Devices Available',
|
||||||
|
style: context.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: ColorsManager.lightGrayColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DataCell.empty,
|
||||||
|
DataCell.empty,
|
||||||
|
DataCell.empty,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: List.generate(widget.productAllocations.length, (index) {
|
||||||
|
final productAllocation = widget.productAllocations[index];
|
||||||
|
final controller = _controllers[index];
|
||||||
|
|
||||||
return DataRow(
|
return DataRow(
|
||||||
cells: [
|
cells: [
|
||||||
DataCell(Text((index + 1).toString())),
|
DataCell(Text((index + 1).toString())),
|
||||||
DataCell(
|
DataCell(
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
productAllocation.product.name,
|
productAllocation.product.name,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
)),
|
)),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Container(
|
Container(
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: ColorsManager.lightGrayColor,
|
color: ColorsManager.lightGrayColor,
|
||||||
width: 1,
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
color: ColorsManager.lightGreyColor,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
// TODO: Delete the product allocation
|
||||||
|
},
|
||||||
|
tooltip: 'Delete Tag',
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
DataCell(
|
||||||
child: IconButton(
|
Container(
|
||||||
icon: const Icon(
|
alignment: Alignment.centerLeft,
|
||||||
Icons.close,
|
width: double.infinity,
|
||||||
color: ColorsManager.lightGreyColor,
|
child: ProductTagField(
|
||||||
size: 16,
|
key: ValueKey(
|
||||||
|
'dropdown_${const Uuid().v4()}_$index'),
|
||||||
|
productName: productAllocation.product.uuid,
|
||||||
|
initialValue: null,
|
||||||
|
onSelected: (value) {
|
||||||
|
controller.text = value.name;
|
||||||
|
},
|
||||||
|
items: tags,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
DataCell(
|
||||||
// TODO: Delete the product allocation
|
SizedBox(
|
||||||
},
|
width: double.infinity,
|
||||||
tooltip: 'Delete Tag',
|
child: DialogDropdown(
|
||||||
padding: EdgeInsets.zero,
|
items: const [],
|
||||||
constraints: const BoxConstraints(),
|
// items: widget.locations,
|
||||||
),
|
selectedValue:
|
||||||
),
|
productAllocation.tag.name.isEmpty
|
||||||
],
|
? 'Main Space'
|
||||||
),
|
: productAllocation.tag.name,
|
||||||
),
|
onSelected: (value) {},
|
||||||
DataCell(
|
)),
|
||||||
Container(
|
),
|
||||||
alignment: Alignment.centerLeft,
|
],
|
||||||
width: double.infinity,
|
);
|
||||||
child: ProductTagField(
|
}),
|
||||||
key: ValueKey('dropdown_${const Uuid().v4()}_$index'),
|
),
|
||||||
productName: productAllocation.product.uuid,
|
),
|
||||||
initialValue: null,
|
_ => const SizedBox.shrink(),
|
||||||
onSelected: (value) {
|
};
|
||||||
controller.text = value.name;
|
},
|
||||||
},
|
|
||||||
items: const [
|
|
||||||
Tag(
|
|
||||||
uuid: '',
|
|
||||||
name: 'Tag',
|
|
||||||
createdAt: '',
|
|
||||||
updatedAt: '',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
DataCell(
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: DialogDropdown(
|
|
||||||
items: const [],
|
|
||||||
// items: widget.locations,
|
|
||||||
selectedValue: productAllocation.tag.name.isEmpty
|
|
||||||
? 'Main Space'
|
|
||||||
: productAllocation.tag.name,
|
|
||||||
onSelected: (value) {},
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user