mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-11-27 11:44:56 +00:00
Compare commits
108 Commits
SP-1448-FE
...
build_main
| Author | SHA1 | Date | |
|---|---|---|---|
| aa3b79bdaf | |||
| 0e31a3ea96 | |||
| fd192894cd | |||
| 692c9e7792 | |||
| 08a9a5c71f | |||
| 7eb1d5b0b0 | |||
| 0d5734a236 | |||
| e22bab00d9 | |||
| d2a2d391e0 | |||
| 1d30c753f5 | |||
| ca02de2093 | |||
| 8f7bfa984b | |||
| 8e9278c93c | |||
| 15d3a05553 | |||
| a1d7457065 | |||
| c99b32fb81 | |||
| 321df401fd | |||
| ee244fa5ed | |||
| 1db069e9a5 | |||
| cf9bafef4d | |||
| 2c73dd6c31 | |||
| 6ec20e2d72 | |||
| 4feae9ad87 | |||
| 52046909d5 | |||
| fc81555be3 | |||
| 8967852ca8 | |||
| a87e79878b | |||
| 056e7372e0 | |||
| d69d867120 | |||
| 644fe56478 | |||
| 766a39f161 | |||
| c97dd40b05 | |||
| 0b65c58947 | |||
| e0951aa13d | |||
| 9e8ebf3768 | |||
| b593e75c67 | |||
| 9eaa367d32 | |||
| 24372a0618 | |||
| 8988947694 | |||
| ef875ef7dc | |||
| 94e4fbd5db | |||
| 302ef36b17 | |||
| c508d016c2 | |||
| e0ad7855d3 | |||
| ecf588cfcb | |||
| c9d15d102b | |||
| 64a29681de | |||
| 02b07cfdb6 | |||
| 0a94557eee | |||
| 4f8d1c4ffd | |||
| 06b320a75d | |||
| 000fe70663 | |||
| 4257f7f0f3 | |||
| b2bf3866a9 | |||
| a15b5439f0 | |||
| fd2a09cada | |||
| 4c2802acfc | |||
| 15343be258 | |||
| c21842cc6d | |||
| 4326559e14 | |||
| 4ded7d5202 | |||
| 625f737791 | |||
| 494ae1c941 | |||
| f67d0e2912 | |||
| 17aad13b2a | |||
| a849c1dafb | |||
| 3e3e17019a | |||
| b1bae3cb15 | |||
| 051bf657ed | |||
| 5191c1e456 | |||
| 7a073f10aa | |||
| 900d47faae | |||
| e35a7fdc70 | |||
| d80f5e1f3a | |||
| baaf5111b1 | |||
| 745205063e | |||
| c07b53107e | |||
| 39d125ac7e | |||
| ad15d0e138 | |||
| e6d272a60d | |||
| 8dfe8d10d4 | |||
| 5279020d08 | |||
| da481536c4 | |||
| f21366268a | |||
| c3aef736fd | |||
| 887ac58f40 | |||
| c709477500 | |||
| 63e7b3faa2 | |||
| 0e61e52bf8 | |||
| 7515b347ce | |||
| 3dfbcb5935 | |||
| 4fd4a9b5bf | |||
| 14fa1b355e | |||
| 78d4e58996 | |||
| 23b9cb5b78 | |||
| 401d0a9788 | |||
| ac2b0d3fac | |||
| 3be7a377c0 | |||
| e4ee456384 | |||
| f02c5d71ba | |||
| d45ff262c7 | |||
| ad227febc1 | |||
| a9d6c6f4ee | |||
| 4d9e57c8b5 | |||
| d1bb8da484 | |||
| 300f9ae358 | |||
| c1dab3400b | |||
| 46815585cb |
1
.gitignore
vendored
1
.gitignore
vendored
@ -30,6 +30,7 @@ migrate_working_dir/
|
|||||||
.pub-cache/
|
.pub-cache/
|
||||||
.pub/
|
.pub/
|
||||||
/build/
|
/build/
|
||||||
|
pubspec.lock
|
||||||
|
|
||||||
# Symbolication related
|
# Symbolication related
|
||||||
app.*.symbols
|
app.*.symbols
|
||||||
|
|||||||
BIN
assets/images/web_Background.png
Normal file
BIN
assets/images/web_Background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
@ -17,7 +17,8 @@ class TagDialogTextfieldDropdown extends StatefulWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_DialogTextfieldDropdownState createState() => _DialogTextfieldDropdownState();
|
_DialogTextfieldDropdownState createState() =>
|
||||||
|
_DialogTextfieldDropdownState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
|
class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
|
||||||
@ -36,6 +37,12 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
|
|||||||
|
|
||||||
_focusNode.addListener(() {
|
_focusNode.addListener(() {
|
||||||
if (!_focusNode.hasFocus) {
|
if (!_focusNode.hasFocus) {
|
||||||
|
// Call onSelected when focus is lost
|
||||||
|
final selectedTag = _filteredItems.firstWhere(
|
||||||
|
(tag) => tag.tag == _controller.text,
|
||||||
|
orElse: () => Tag(tag: _controller.text),
|
||||||
|
);
|
||||||
|
widget.onSelected(selectedTag);
|
||||||
_closeDropdown();
|
_closeDropdown();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -43,7 +50,9 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
|
|||||||
|
|
||||||
void _filterItems() {
|
void _filterItems() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_filteredItems = widget.items.where((tag) => tag.product?.uuid == widget.product).toList();
|
_filteredItems = widget.items;
|
||||||
|
// .where((tag) => tag.product?.uuid == widget.product)
|
||||||
|
// .toList();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +121,9 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
|
|||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.bodyMedium
|
.bodyMedium
|
||||||
?.copyWith(color: ColorsManager.textPrimaryColor)),
|
?.copyWith(
|
||||||
|
color: ColorsManager
|
||||||
|
.textPrimaryColor)),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_controller.text = tag.tag ?? '';
|
_controller.text = tag.tag ?? '';
|
||||||
widget.onSelected(tag);
|
widget.onSelected(tag);
|
||||||
@ -156,13 +167,15 @@ class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
|
|||||||
controller: _controller,
|
controller: _controller,
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
onFieldSubmitted: (value) {
|
onFieldSubmitted: (value) {
|
||||||
final selectedTag = _filteredItems.firstWhere((tag) => tag.tag == value,
|
final selectedTag = _filteredItems.firstWhere(
|
||||||
|
(tag) => tag.tag == value,
|
||||||
orElse: () => Tag(tag: value));
|
orElse: () => Tag(tag: value));
|
||||||
widget.onSelected(selectedTag);
|
widget.onSelected(selectedTag);
|
||||||
_closeDropdown();
|
_closeDropdown();
|
||||||
},
|
},
|
||||||
onTapOutside: (event) {
|
onTapOutside: (event) {
|
||||||
widget.onSelected(_filteredItems.firstWhere((tag) => tag.tag == _controller.text,
|
widget.onSelected(_filteredItems.firstWhere(
|
||||||
|
(tag) => tag.tag == _controller.text,
|
||||||
orElse: () => Tag(tag: _controller.text)));
|
orElse: () => Tag(tag: _controller.text)));
|
||||||
_closeDropdown();
|
_closeDropdown();
|
||||||
},
|
},
|
||||||
|
|||||||
16
lib/core/network/custom_exceptions.dart
Normal file
16
lib/core/network/custom_exceptions.dart
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
import 'enums.dart';
|
||||||
|
|
||||||
|
class GenericException implements Exception {
|
||||||
|
final ExceptionType type;
|
||||||
|
final String errorMessage;
|
||||||
|
const GenericException(
|
||||||
|
{required this.type, this.errorMessage = "Unknown Error"});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Object?> get props => [type, errorMessage];
|
||||||
|
}
|
||||||
36
lib/core/network/dio.dart
Normal file
36
lib/core/network/dio.dart
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
|
||||||
|
import '../../utils/constants/api_const.dart';
|
||||||
|
|
||||||
|
class DioInstance {
|
||||||
|
Dio? _dio;
|
||||||
|
|
||||||
|
Dio get dio => _dio ?? _instantiate();
|
||||||
|
|
||||||
|
String? baseUrl;
|
||||||
|
|
||||||
|
DioInstance({this.baseUrl});
|
||||||
|
|
||||||
|
Dio _instantiate() {
|
||||||
|
Dio dio = Dio(
|
||||||
|
BaseOptions(
|
||||||
|
baseUrl: baseUrl ?? '${ApiEndpoints.baseUrl}/',
|
||||||
|
receiveDataWhenStatusError: true,
|
||||||
|
headers: {
|
||||||
|
'content_Type': 'application/json',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
dio.interceptors.add(
|
||||||
|
LogInterceptor(
|
||||||
|
responseHeader: false,
|
||||||
|
requestHeader: false,
|
||||||
|
requestBody: true,
|
||||||
|
responseBody: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return dio;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
lib/core/network/end_points.dart
Normal file
3
lib/core/network/end_points.dart
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
class EndPoints {
|
||||||
|
static const String fetchCommunities = 'projects/{projectUuid}/communities';
|
||||||
|
}
|
||||||
50
lib/core/network/enums.dart
Normal file
50
lib/core/network/enums.dart
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
enum RequestType {
|
||||||
|
get,
|
||||||
|
post,
|
||||||
|
delete,
|
||||||
|
put,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ExceptionType {
|
||||||
|
notAuthenticated,
|
||||||
|
connectionError,
|
||||||
|
// related to http status code exceptions
|
||||||
|
notAuthorized,
|
||||||
|
notFound,
|
||||||
|
internalServerException,
|
||||||
|
serviceUnavailableException,
|
||||||
|
pageGone,
|
||||||
|
|
||||||
|
// related to bad request status code
|
||||||
|
// related to auth requests
|
||||||
|
invalidCredentials,
|
||||||
|
solutionAlreadySunmitted,
|
||||||
|
invalidValidation,
|
||||||
|
// other
|
||||||
|
other,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ExceptionMessage {
|
||||||
|
NOT_AUTHENTICATED,
|
||||||
|
INVALID_CREDENTIALS,
|
||||||
|
The_password_field_must_be_at_least_8_characters,
|
||||||
|
SOLUTION_ALREADY_SUBMITTED,
|
||||||
|
you_are_not_authorized,
|
||||||
|
page_not_found,
|
||||||
|
page_gone,
|
||||||
|
INTERNAL_SERVER_ERROR,
|
||||||
|
service_unavailable,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NotificationsType {
|
||||||
|
payment,
|
||||||
|
transporation,
|
||||||
|
product,
|
||||||
|
zero,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ImagesType {
|
||||||
|
assets,
|
||||||
|
svg,
|
||||||
|
network,
|
||||||
|
}
|
||||||
121
lib/core/network/request.dart
Normal file
121
lib/core/network/request.dart
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
|
||||||
|
import '../../pages/auth/model/token.dart';
|
||||||
|
import 'custom_exceptions.dart';
|
||||||
|
import 'dio.dart';
|
||||||
|
import 'enums.dart';
|
||||||
|
|
||||||
|
class Request {
|
||||||
|
String endPoint;
|
||||||
|
bool? autherized;
|
||||||
|
bool? isFormData;
|
||||||
|
RequestType? method;
|
||||||
|
Map<String, dynamic>? headers;
|
||||||
|
final Map<String, dynamic>? queryParams;
|
||||||
|
Map<String, dynamic>? body;
|
||||||
|
Duration? receiveTimeout;
|
||||||
|
Request(
|
||||||
|
this.endPoint, {
|
||||||
|
this.autherized,
|
||||||
|
this.isFormData,
|
||||||
|
this.method,
|
||||||
|
this.headers,
|
||||||
|
this.queryParams,
|
||||||
|
this.body,
|
||||||
|
this.receiveTimeout,
|
||||||
|
}) {
|
||||||
|
headers = {
|
||||||
|
'content_Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Future<Map<String, dynamic>> sendRequest() async {
|
||||||
|
Response? response;
|
||||||
|
if (autherized != null && autherized!) {
|
||||||
|
final storage = const FlutterSecureStorage();
|
||||||
|
final token = await storage.read(key: Token.loginAccessTokenKey);
|
||||||
|
if (token != null) {
|
||||||
|
headers!["authorization"] = "Bearer $token";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
response = await DioInstance().dio.request(
|
||||||
|
endPoint,
|
||||||
|
queryParameters: queryParams,
|
||||||
|
data: isFormData != null && isFormData == true
|
||||||
|
? FormData.fromMap(body!)
|
||||||
|
: body,
|
||||||
|
options: Options(
|
||||||
|
method: method!.name.toUpperCase(),
|
||||||
|
headers: headers,
|
||||||
|
contentType: 'application/json',
|
||||||
|
receiveTimeout: receiveTimeout,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (response.statusCode! >= 200 && response.statusCode! < 300) {
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
} on DioException catch (error) {
|
||||||
|
if (error.type == DioExceptionType.badResponse) {
|
||||||
|
throw badRequestException[error.response!.data["error"]] ??
|
||||||
|
const GenericException(
|
||||||
|
type: ExceptionType.other,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (error.type == DioExceptionType.connectionError ||
|
||||||
|
error.type == DioExceptionType.connectionTimeout ||
|
||||||
|
error.type == DioExceptionType.receiveTimeout ||
|
||||||
|
error.type == DioExceptionType.sendTimeout ||
|
||||||
|
error.type == DioExceptionType.unknown) {
|
||||||
|
throw const GenericException(
|
||||||
|
type: ExceptionType.connectionError,
|
||||||
|
errorMessage: 'no_internet_connection',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'endPoint': endPoint,
|
||||||
|
'method': method!.name.toUpperCase(),
|
||||||
|
'body': json.encode(body),
|
||||||
|
'headers': headers,
|
||||||
|
'queryParams': queryParams,
|
||||||
|
'autherized': autherized,
|
||||||
|
'isFormData': isFormData,
|
||||||
|
};
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return jsonEncode(toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Object?> get props => [
|
||||||
|
endPoint,
|
||||||
|
autherized,
|
||||||
|
isFormData,
|
||||||
|
method,
|
||||||
|
headers,
|
||||||
|
queryParams,
|
||||||
|
body,
|
||||||
|
receiveTimeout,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, GenericException> badRequestException = {
|
||||||
|
ExceptionMessage.INVALID_CREDENTIALS.name: const GenericException(
|
||||||
|
type: ExceptionType.invalidCredentials,
|
||||||
|
errorMessage: "Invalid credentials ...",
|
||||||
|
),
|
||||||
|
"The password field must be at least 8 characters.": const GenericException(
|
||||||
|
type: ExceptionType.invalidValidation,
|
||||||
|
errorMessage: 'password must be 8 or more characters',
|
||||||
|
),
|
||||||
|
ExceptionMessage.NOT_AUTHENTICATED.name: const GenericException(
|
||||||
|
type: ExceptionType.notAuthenticated,
|
||||||
|
errorMessage: "not authenticated",
|
||||||
|
),
|
||||||
|
};
|
||||||
71
lib/pages/analytics/models/analytics_device.dart
Normal file
71
lib/pages/analytics/models/analytics_device.dart
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
class AnalyticsDevice {
|
||||||
|
const AnalyticsDevice({
|
||||||
|
required this.uuid,
|
||||||
|
required this.name,
|
||||||
|
this.createdAt,
|
||||||
|
this.updatedAt,
|
||||||
|
this.deviceTuyaUuid,
|
||||||
|
this.isActive,
|
||||||
|
this.productDevice,
|
||||||
|
this.spaceUuid,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String uuid;
|
||||||
|
final String name;
|
||||||
|
final DateTime? createdAt;
|
||||||
|
final DateTime? updatedAt;
|
||||||
|
final String? deviceTuyaUuid;
|
||||||
|
final bool? isActive;
|
||||||
|
final ProductDevice? productDevice;
|
||||||
|
final String? spaceUuid;
|
||||||
|
|
||||||
|
factory AnalyticsDevice.fromJson(Map<String, dynamic> json) {
|
||||||
|
return AnalyticsDevice(
|
||||||
|
uuid: json['uuid'] as String,
|
||||||
|
name: json['name'] as String,
|
||||||
|
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null,
|
||||||
|
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null,
|
||||||
|
deviceTuyaUuid: json['deviceTuyaUuid'] as String?,
|
||||||
|
isActive: json['isActive'] as bool?,
|
||||||
|
productDevice: json['productDevice'] != null
|
||||||
|
? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>)
|
||||||
|
: null,
|
||||||
|
spaceUuid: (json['spaces'] as List<dynamic>?)
|
||||||
|
?.map((e) => e['uuid'])
|
||||||
|
.firstOrNull
|
||||||
|
?.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProductDevice {
|
||||||
|
const ProductDevice({
|
||||||
|
this.uuid,
|
||||||
|
this.createdAt,
|
||||||
|
this.updatedAt,
|
||||||
|
this.catName,
|
||||||
|
this.prodId,
|
||||||
|
this.name,
|
||||||
|
this.prodType,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String? uuid;
|
||||||
|
final DateTime? createdAt;
|
||||||
|
final DateTime? updatedAt;
|
||||||
|
final String? catName;
|
||||||
|
final String? prodId;
|
||||||
|
final String? name;
|
||||||
|
final String? prodType;
|
||||||
|
|
||||||
|
factory ProductDevice.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ProductDevice(
|
||||||
|
uuid: json['uuid'] as String?,
|
||||||
|
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null,
|
||||||
|
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null,
|
||||||
|
catName: json['catName'] as String?,
|
||||||
|
prodId: json['prodId'] as String?,
|
||||||
|
name: json['name'] as String?,
|
||||||
|
prodType: json['prodType'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,18 +1,32 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
class Occupacy extends Equatable {
|
class Occupacy extends Equatable {
|
||||||
final String date;
|
final DateTime date;
|
||||||
final String occupancy;
|
final String occupancy;
|
||||||
|
final String spaceUuid;
|
||||||
|
final int occupiedSeconds;
|
||||||
|
|
||||||
const Occupacy({required this.date, required this.occupancy});
|
const Occupacy({
|
||||||
|
required this.date,
|
||||||
|
required this.occupancy,
|
||||||
|
required this.spaceUuid,
|
||||||
|
required this.occupiedSeconds,
|
||||||
|
});
|
||||||
|
|
||||||
factory Occupacy.fromJson(Map<String, dynamic> json) {
|
factory Occupacy.fromJson(Map<String, dynamic> json) {
|
||||||
return Occupacy(
|
return Occupacy(
|
||||||
date: json['date'] as String,
|
date: DateTime.parse(json['event_date'] as String? ?? '${DateTime.now()}'),
|
||||||
occupancy: json['occupancy'] as String,
|
occupancy: (json['occupancy_percentage'] ?? 0).toString(),
|
||||||
|
spaceUuid: json['space_uuid'] as String? ?? '',
|
||||||
|
occupiedSeconds: json['occupied_seconds'] as int? ?? 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [date, occupancy];
|
List<Object?> get props => [
|
||||||
|
date,
|
||||||
|
occupancy,
|
||||||
|
spaceUuid,
|
||||||
|
occupiedSeconds,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,28 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
class OccupancyHeatMapModel extends Equatable {
|
class OccupancyHeatMapModel extends Equatable {
|
||||||
final DateTime date;
|
final String uuid;
|
||||||
|
|
||||||
final int occupancy;
|
final DateTime eventDate;
|
||||||
|
|
||||||
|
final int countTotalPresenceDetected;
|
||||||
|
|
||||||
const OccupancyHeatMapModel({
|
const OccupancyHeatMapModel({
|
||||||
required this.date,
|
required this.uuid,
|
||||||
required this.occupancy,
|
required this.eventDate,
|
||||||
|
required this.countTotalPresenceDetected,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory OccupancyHeatMapModel.fromJson(Map<String, dynamic> json) {
|
factory OccupancyHeatMapModel.fromJson(Map<String, dynamic> json) {
|
||||||
return OccupancyHeatMapModel(
|
return OccupancyHeatMapModel(
|
||||||
date: DateTime.parse(json['date'] as String),
|
uuid: json['uuid'] as String? ?? '',
|
||||||
occupancy: json['occupancy'] as int,
|
eventDate: DateTime.parse(
|
||||||
|
json['event_date'] as String? ?? '${DateTime.now()}',
|
||||||
|
),
|
||||||
|
countTotalPresenceDetected: json['count_total_presence_detected'] as int? ?? 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [date, occupancy];
|
List<Object?> get props => [uuid, eventDate, countTotalPresenceDetected];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,27 +1,66 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
class PhasesEnergyConsumption extends Equatable {
|
class PhasesEnergyConsumption extends Equatable {
|
||||||
final int month;
|
final String uuid;
|
||||||
final double phaseA;
|
final DateTime createdAt;
|
||||||
final double phaseB;
|
final DateTime updatedAt;
|
||||||
final double phaseC;
|
final String deviceUuid;
|
||||||
|
final DateTime date;
|
||||||
|
final double energyConsumedKw;
|
||||||
|
final double energyConsumedA;
|
||||||
|
final double energyConsumedB;
|
||||||
|
final double energyConsumedC;
|
||||||
|
|
||||||
const PhasesEnergyConsumption({
|
const PhasesEnergyConsumption({
|
||||||
required this.month,
|
required this.uuid,
|
||||||
required this.phaseA,
|
required this.createdAt,
|
||||||
required this.phaseB,
|
required this.updatedAt,
|
||||||
required this.phaseC,
|
required this.deviceUuid,
|
||||||
|
required this.date,
|
||||||
|
required this.energyConsumedKw,
|
||||||
|
required this.energyConsumedA,
|
||||||
|
required this.energyConsumedB,
|
||||||
|
required this.energyConsumedC,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [month, phaseA, phaseB, phaseC];
|
List<Object?> get props => [
|
||||||
|
uuid,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
deviceUuid,
|
||||||
|
date,
|
||||||
|
energyConsumedKw,
|
||||||
|
energyConsumedA,
|
||||||
|
energyConsumedB,
|
||||||
|
energyConsumedC,
|
||||||
|
];
|
||||||
|
|
||||||
factory PhasesEnergyConsumption.fromJson(Map<String, dynamic> json) {
|
factory PhasesEnergyConsumption.fromJson(Map<String, dynamic> json) {
|
||||||
return PhasesEnergyConsumption(
|
return PhasesEnergyConsumption(
|
||||||
month: json['month'] as int,
|
uuid: json['uuid'] as String,
|
||||||
phaseA: (json['phaseA'] as num).toDouble(),
|
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||||
phaseB: (json['phaseB'] as num).toDouble(),
|
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||||||
phaseC: (json['phaseC'] as num).toDouble(),
|
deviceUuid: json['deviceUuid'] as String,
|
||||||
|
date: DateTime.parse(json['date'] as String),
|
||||||
|
energyConsumedKw: double.parse(json['energyConsumedKw']),
|
||||||
|
energyConsumedA: double.parse(json['energyConsumedA']),
|
||||||
|
energyConsumedB: double.parse(json['energyConsumedB']),
|
||||||
|
energyConsumedC: double.parse(json['energyConsumedC']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'uuid': uuid,
|
||||||
|
'createdAt': createdAt.toIso8601String(),
|
||||||
|
'updatedAt': updatedAt.toIso8601String(),
|
||||||
|
'deviceUuid': deviceUuid,
|
||||||
|
'date': date.toIso8601String().split('T')[0],
|
||||||
|
'energyConsumedKw': energyConsumedKw.toString(),
|
||||||
|
'energyConsumedA': energyConsumedA.toString(),
|
||||||
|
'energyConsumedB': energyConsumedB.toString(),
|
||||||
|
'energyConsumedC': energyConsumedC.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,69 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart';
|
||||||
|
|
||||||
|
part 'analytics_devices_event.dart';
|
||||||
|
part 'analytics_devices_state.dart';
|
||||||
|
|
||||||
|
class AnalyticsDevicesBloc
|
||||||
|
extends Bloc<AnalyticsDevicesEvent, AnalyticsDevicesState> {
|
||||||
|
AnalyticsDevicesBloc(
|
||||||
|
this._analyticsDevicesService,
|
||||||
|
) : super(const AnalyticsDevicesState()) {
|
||||||
|
on<LoadAnalyticsDevicesEvent>(_onLoadAnalyticsDevices);
|
||||||
|
on<SelectAnalyticsDeviceEvent>(_onSelectAnalyticsDevice);
|
||||||
|
on<ClearAnalyticsDeviceEvent>(_onClearAnalyticsDevice);
|
||||||
|
}
|
||||||
|
final AnalyticsDevicesService _analyticsDevicesService;
|
||||||
|
|
||||||
|
Future<void> _onLoadAnalyticsDevices(
|
||||||
|
LoadAnalyticsDevicesEvent event,
|
||||||
|
Emitter<AnalyticsDevicesState> emit,
|
||||||
|
) async {
|
||||||
|
emit(const AnalyticsDevicesState(status: AnalyticsDevicesStatus.loading));
|
||||||
|
|
||||||
|
try {
|
||||||
|
final devices = await _analyticsDevicesService.getDevices(event.param);
|
||||||
|
emit(
|
||||||
|
AnalyticsDevicesState(
|
||||||
|
status: AnalyticsDevicesStatus.loaded,
|
||||||
|
devices: devices,
|
||||||
|
selectedDevice: devices.firstOrNull,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (devices.isNotEmpty) {
|
||||||
|
event.onSuccess(devices.first);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
AnalyticsDevicesState(
|
||||||
|
status: AnalyticsDevicesStatus.failure,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSelectAnalyticsDevice(
|
||||||
|
SelectAnalyticsDeviceEvent event,
|
||||||
|
Emitter<AnalyticsDevicesState> emit,
|
||||||
|
) {
|
||||||
|
emit(
|
||||||
|
AnalyticsDevicesState(
|
||||||
|
selectedDevice: event.device,
|
||||||
|
devices: state.devices,
|
||||||
|
errorMessage: state.errorMessage,
|
||||||
|
status: state.status,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onClearAnalyticsDevice(
|
||||||
|
ClearAnalyticsDeviceEvent event,
|
||||||
|
Emitter<AnalyticsDevicesState> emit,
|
||||||
|
) {
|
||||||
|
emit(const AnalyticsDevicesState());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
part of 'analytics_devices_bloc.dart';
|
||||||
|
|
||||||
|
sealed class AnalyticsDevicesEvent extends Equatable {
|
||||||
|
const AnalyticsDevicesEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class LoadAnalyticsDevicesEvent extends AnalyticsDevicesEvent {
|
||||||
|
const LoadAnalyticsDevicesEvent({required this.param, required this.onSuccess});
|
||||||
|
|
||||||
|
final GetAnalyticsDevicesParam param;
|
||||||
|
final void Function(AnalyticsDevice device) onSuccess;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [param];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class SelectAnalyticsDeviceEvent extends AnalyticsDevicesEvent {
|
||||||
|
const SelectAnalyticsDeviceEvent(this.device);
|
||||||
|
|
||||||
|
final AnalyticsDevice device;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [device];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ClearAnalyticsDeviceEvent extends AnalyticsDevicesEvent {
|
||||||
|
const ClearAnalyticsDeviceEvent();
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
part of 'analytics_devices_bloc.dart';
|
||||||
|
|
||||||
|
enum AnalyticsDevicesStatus { initial, loading, loaded, failure }
|
||||||
|
|
||||||
|
final class AnalyticsDevicesState extends Equatable {
|
||||||
|
const AnalyticsDevicesState({
|
||||||
|
this.status = AnalyticsDevicesStatus.initial,
|
||||||
|
this.devices = const [],
|
||||||
|
this.errorMessage,
|
||||||
|
this.selectedDevice,
|
||||||
|
});
|
||||||
|
|
||||||
|
final AnalyticsDevicesStatus status;
|
||||||
|
final List<AnalyticsDevice> devices;
|
||||||
|
final AnalyticsDevice? selectedDevice;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [status, devices, errorMessage, selectedDevice];
|
||||||
|
}
|
||||||
@ -20,6 +20,13 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
|||||||
spaces,
|
spaces,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||||
|
if (spaceTreeState.selectedCommunities.contains(community.uuid)) {
|
||||||
|
clearData(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
FetchEnergyManagementDataHelper.loadEnergyManagementData(
|
FetchEnergyManagementDataHelper.loadEnergyManagementData(
|
||||||
context,
|
context,
|
||||||
communityId: community.uuid,
|
communityId: community.uuid,
|
||||||
@ -41,6 +48,13 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||||
|
if (spaceTreeState.selectedCommunities.contains(community.uuid) ||
|
||||||
|
spaceTreeState.selectedSpaces.contains(space.uuid)) {
|
||||||
|
clearData(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
FetchEnergyManagementDataHelper.loadEnergyManagementData(
|
FetchEnergyManagementDataHelper.loadEnergyManagementData(
|
||||||
context,
|
context,
|
||||||
communityId: community.uuid,
|
communityId: community.uuid,
|
||||||
@ -54,7 +68,9 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
|||||||
CommunityModel community,
|
CommunityModel community,
|
||||||
SpaceModel child,
|
SpaceModel child,
|
||||||
) {
|
) {
|
||||||
// Do nothing
|
if (child.children.isNotEmpty) {
|
||||||
|
return onSpaceSelected(context, community, child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -14,17 +14,7 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
|
|||||||
CommunityModel community,
|
CommunityModel community,
|
||||||
List<SpaceModel> spaces,
|
List<SpaceModel> spaces,
|
||||||
) {
|
) {
|
||||||
context.read<SpaceTreeBloc>().add(
|
// Do Nothing
|
||||||
OnCommunitySelected(
|
|
||||||
community.uuid,
|
|
||||||
spaces.isNotEmpty ? [spaces.first] : [],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
FetchOccupancyDataHelper.loadOccupancyData(
|
|
||||||
context,
|
|
||||||
communityId: community.uuid,
|
|
||||||
spaceId: spaces.isNotEmpty ? spaces.first.uuid ?? '' : '',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -34,19 +24,17 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
|
|||||||
SpaceModel space,
|
SpaceModel space,
|
||||||
) {
|
) {
|
||||||
final spaceTreeBloc = context.read<SpaceTreeBloc>();
|
final spaceTreeBloc = context.read<SpaceTreeBloc>();
|
||||||
final selectedSpacesIds = spaceTreeBloc.state.selectedSpaces;
|
final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
|
||||||
final isSpaceSelected = selectedSpacesIds.contains(space.uuid);
|
|
||||||
|
|
||||||
if (selectedSpacesIds.isEmpty) {
|
if (isSpaceSelected) {
|
||||||
spaceTreeBloc.add(OnCommunitySelected(community.uuid, [space]));
|
clearData(context);
|
||||||
} else if (isSpaceSelected) {
|
return;
|
||||||
spaceTreeBloc.add(const SpaceTreeClearSelectionEvent());
|
|
||||||
} else {
|
|
||||||
spaceTreeBloc
|
|
||||||
..add(const SpaceTreeClearSelectionEvent())
|
|
||||||
..add(OnSpaceSelected(community, space.uuid ?? '', []));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spaceTreeBloc
|
||||||
|
..add(const SpaceTreeClearSelectionEvent())
|
||||||
|
..add(OnSpaceSelected(community, space.uuid ?? '', []));
|
||||||
|
|
||||||
FetchOccupancyDataHelper.loadOccupancyData(
|
FetchOccupancyDataHelper.loadOccupancyData(
|
||||||
context,
|
context,
|
||||||
communityId: community.uuid,
|
communityId: community.uuid,
|
||||||
@ -60,12 +48,12 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
|
|||||||
CommunityModel community,
|
CommunityModel community,
|
||||||
SpaceModel child,
|
SpaceModel child,
|
||||||
) {
|
) {
|
||||||
// Do nothing
|
onSpaceSelected(context, community, child);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void clearData(BuildContext context) {
|
void clearData(BuildContext context) {
|
||||||
context.read<SpaceTreeBloc>().add(const SpaceTreeClearSelectionEvent());
|
context.read<SpaceTreeBloc>().add(const SpaceTreeClearSelectionEvent());
|
||||||
// FetchOccupancyDataHelper.clearAllData(context);
|
FetchOccupancyDataHelper.clearAllData(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_communities_sidebar.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_communities_sidebar.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart';
|
||||||
@ -11,10 +12,13 @@ import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/real
|
|||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/fake_energy_consumption_by_phases_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/fake_energy_consumption_per_device_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/occupacy/fake_occupacy_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/fake_occupancy_heat_map_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/remote_energy_consumption_by_phases_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/remote_energy_consumption_per_device_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/occupacy/remote_occupancy_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/remote_occupancy_heat_map_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/remote_power_clamp_info_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/remote_power_clamp_info_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/remote_total_energy_consumption_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/remote_total_energy_consumption_service.dart';
|
||||||
@ -23,9 +27,22 @@ import 'package:syncrow_web/services/api/http_service.dart';
|
|||||||
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
||||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||||
|
|
||||||
class AnalyticsPage extends StatelessWidget {
|
class AnalyticsPage extends StatefulWidget {
|
||||||
const AnalyticsPage({super.key});
|
const AnalyticsPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AnalyticsPage> createState() => _AnalyticsPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AnalyticsPageState extends State<AnalyticsPage> {
|
||||||
|
late final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_httpService = HTTPService();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
@ -35,22 +52,22 @@ class AnalyticsPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => TotalEnergyConsumptionBloc(
|
create: (context) => TotalEnergyConsumptionBloc(
|
||||||
RemoteTotalEnergyConsumptionService(HTTPService()),
|
RemoteTotalEnergyConsumptionService(_httpService),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => EnergyConsumptionByPhasesBloc(
|
create: (context) => EnergyConsumptionByPhasesBloc(
|
||||||
FakeEnergyConsumptionByPhasesService(),
|
RemoteEnergyConsumptionByPhasesService(_httpService),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => EnergyConsumptionPerDeviceBloc(
|
create: (context) => EnergyConsumptionPerDeviceBloc(
|
||||||
FakeEnergyConsumptionPerDeviceService(),
|
RemoteEnergyConsumptionPerDeviceService(_httpService),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => PowerClampInfoBloc(
|
create: (context) => PowerClampInfoBloc(
|
||||||
RemotePowerClampInfoService(HTTPService()),
|
RemotePowerClampInfoService(_httpService),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider<RealtimeDeviceChangesBloc>(
|
BlocProvider<RealtimeDeviceChangesBloc>(
|
||||||
@ -58,11 +75,25 @@ class AnalyticsPage extends StatelessWidget {
|
|||||||
FirebaseRealtimeDeviceService(),
|
FirebaseRealtimeDeviceService(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocProvider(create: (context) => OccupancyBloc(FakeOccupacyService())),
|
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => OccupancyHeatMapBloc(FakeOccupancyHeatMapService()),
|
create: (context) => OccupancyBloc(
|
||||||
|
RemoteOccupancyService(_httpService),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => OccupancyHeatMapBloc(
|
||||||
|
RemoteOccupancyHeatMapService(_httpService),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
BlocProvider(create: (context) => AnalyticsDatePickerBloc()),
|
BlocProvider(create: (context) => AnalyticsDatePickerBloc()),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => AnalyticsDevicesBloc(
|
||||||
|
AnalyticsDevicesServiceDelegate(
|
||||||
|
RemoteOccupancyAnalyticsDevicesService(_httpService),
|
||||||
|
RemoteEnergyManagementAnalyticsDevicesService(_httpService),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: const AnalyticsPageForm(),
|
child: const AnalyticsPageForm(),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -14,7 +14,6 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
|
||||||
return BlocBuilder<AnalyticsTabBloc, AnalyticsPageTab>(
|
return BlocBuilder<AnalyticsTabBloc, AnalyticsPageTab>(
|
||||||
buildWhen: (previous, current) => previous != current,
|
buildWhen: (previous, current) => previous != current,
|
||||||
builder: (context, selectedTab) => Column(
|
builder: (context, selectedTab) => Column(
|
||||||
@ -68,15 +67,22 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
|||||||
context.read<AnalyticsDatePickerBloc>().add(
|
context.read<AnalyticsDatePickerBloc>().add(
|
||||||
UpdateAnalyticsDatePickerEvent(montlyDate: value),
|
UpdateAnalyticsDatePickerEvent(montlyDate: value),
|
||||||
);
|
);
|
||||||
FetchEnergyManagementDataHelper.loadEnergyManagementData(
|
|
||||||
context,
|
final spaceTreeState =
|
||||||
selectedDate: value,
|
context.read<SpaceTreeBloc>().state;
|
||||||
communityId:
|
if (spaceTreeState.selectedSpaces.isNotEmpty) {
|
||||||
spaceTreeState.selectedCommunities.firstOrNull ??
|
FetchEnergyManagementDataHelper
|
||||||
'',
|
.loadEnergyManagementData(
|
||||||
spaceId:
|
context,
|
||||||
spaceTreeState.selectedSpaces.firstOrNull ?? '',
|
shouldFetchAnalyticsDevices: false,
|
||||||
);
|
selectedDate: value,
|
||||||
|
communityId:
|
||||||
|
spaceTreeState.selectedCommunities.firstOrNull ??
|
||||||
|
'',
|
||||||
|
spaceId:
|
||||||
|
spaceTreeState.selectedSpaces.firstOrNull ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
selectedDate: context
|
selectedDate: context
|
||||||
.watch<AnalyticsDatePickerBloc>()
|
.watch<AnalyticsDatePickerBloc>()
|
||||||
|
|||||||
@ -45,7 +45,7 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Dialog(
|
return Dialog(
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsetsDirectional.all(20),
|
padding: const EdgeInsetsDirectional.all(20),
|
||||||
width: 320,
|
width: 320,
|
||||||
@ -121,6 +121,7 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Row _buildYearSelector() {
|
Row _buildYearSelector() {
|
||||||
|
final currentYear = DateTime.now().year;
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@ -134,17 +135,35 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
|
|||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => setState(() => _currentYear = _currentYear - 1),
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_currentYear = _currentYear - 1;
|
||||||
|
});
|
||||||
|
},
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.chevron_left,
|
Icons.chevron_left,
|
||||||
color: ColorsManager.grey700,
|
color: ColorsManager.grey700,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => setState(() => _currentYear = _currentYear + 1),
|
onPressed: _currentYear < currentYear
|
||||||
icon: const Icon(
|
? () {
|
||||||
|
setState(() {
|
||||||
|
_currentYear = _currentYear + 1;
|
||||||
|
// Clear selected month if it becomes invalid in the new year
|
||||||
|
if (_currentYear == currentYear &&
|
||||||
|
_selectedMonth != null &&
|
||||||
|
_selectedMonth! > DateTime.now().month - 1) {
|
||||||
|
_selectedMonth = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
icon: Icon(
|
||||||
Icons.chevron_right,
|
Icons.chevron_right,
|
||||||
color: ColorsManager.grey700,
|
color: _currentYear < currentYear
|
||||||
|
? ColorsManager.grey700
|
||||||
|
: ColorsManager.grey700.withValues(alpha: 0.3),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -152,11 +171,13 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildMonthsGrid() {
|
Widget _buildMonthsGrid() {
|
||||||
|
final currentDate = DateTime.now();
|
||||||
|
final isCurrentYear = _currentYear == currentDate.year;
|
||||||
|
|
||||||
return GridView.builder(
|
return GridView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: 12,
|
itemCount: 12,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 3,
|
crossAxisCount: 3,
|
||||||
childAspectRatio: 2.5,
|
childAspectRatio: 2.5,
|
||||||
@ -165,25 +186,43 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
|
|||||||
),
|
),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final isSelected = _selectedMonth == index;
|
final isSelected = _selectedMonth == index;
|
||||||
|
final isFutureMonth = isCurrentYear && index > currentDate.month - 1;
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => setState(() => _selectedMonth = index),
|
onTap: isFutureMonth ? null : () => setState(() => _selectedMonth = index),
|
||||||
child: Container(
|
child: DecoratedBox(
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isSelected
|
color: const Color(0xFFEDF2F7),
|
||||||
? ColorsManager.vividBlue.withValues(alpha: 0.7)
|
borderRadius: BorderRadius.only(
|
||||||
: const Color(0xFFEDF2F7),
|
topLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero,
|
||||||
borderRadius:
|
bottomLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero,
|
||||||
isSelected ? BorderRadius.circular(15) : BorderRadius.zero,
|
topRight: index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
|
||||||
|
bottomRight:
|
||||||
|
index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Container(
|
||||||
_monthNames[index],
|
alignment: Alignment.center,
|
||||||
style: context.textTheme.titleSmall?.copyWith(
|
decoration: BoxDecoration(
|
||||||
fontSize: 12,
|
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? ColorsManager.whiteColors
|
? ColorsManager.vividBlue.withValues(alpha: 0.7)
|
||||||
: ColorsManager.blackColor.withValues(alpha: 0.8),
|
: isFutureMonth
|
||||||
fontWeight: FontWeight.w500,
|
? ColorsManager.grey700.withValues(alpha: 0.1)
|
||||||
|
: const Color(0xFFEDF2F7),
|
||||||
|
borderRadius:
|
||||||
|
isSelected ? BorderRadius.circular(15) : BorderRadius.zero,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
_monthNames[index],
|
||||||
|
style: context.textTheme.titleSmall?.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
color: isSelected
|
||||||
|
? ColorsManager.whiteColors
|
||||||
|
: isFutureMonth
|
||||||
|
? ColorsManager.blackColor.withValues(alpha: 0.3)
|
||||||
|
: ColorsManager.blackColor.withValues(alpha: 0.8),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -20,9 +20,9 @@ class _YearPickerWidgetState extends State<YearPickerWidget> {
|
|||||||
late int _currentYear;
|
late int _currentYear;
|
||||||
|
|
||||||
static final years = List.generate(
|
static final years = List.generate(
|
||||||
DateTime.now().year - 2020 + 1,
|
DateTime.now().year - (DateTime.now().year - 5) + 1,
|
||||||
(index) => (2020 + index),
|
(index) => (2020 + index),
|
||||||
);
|
).where((year) => year <= DateTime.now().year).toList();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -33,7 +33,7 @@ class _YearPickerWidgetState extends State<YearPickerWidget> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Dialog(
|
return Dialog(
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsetsDirectional.all(20),
|
padding: const EdgeInsetsDirectional.all(20),
|
||||||
width: 320,
|
width: 320,
|
||||||
@ -109,7 +109,6 @@ class _YearPickerWidgetState extends State<YearPickerWidget> {
|
|||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: years.length,
|
itemCount: years.length,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 3,
|
crossAxisCount: 3,
|
||||||
childAspectRatio: 2.5,
|
childAspectRatio: 2.5,
|
||||||
@ -120,23 +119,35 @@ class _YearPickerWidgetState extends State<YearPickerWidget> {
|
|||||||
final isSelected = _currentYear == years[index];
|
final isSelected = _currentYear == years[index];
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => setState(() => _currentYear = years[index]),
|
onTap: () => setState(() => _currentYear = years[index]),
|
||||||
child: Container(
|
child: DecoratedBox(
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isSelected
|
color: const Color(0xFFEDF2F7),
|
||||||
? ColorsManager.vividBlue.withValues(alpha: 0.7)
|
borderRadius: BorderRadius.only(
|
||||||
: const Color(0xFFEDF2F7),
|
topLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero,
|
||||||
borderRadius:
|
bottomLeft: index % 3 == 0 ? const Radius.circular(16) : Radius.zero,
|
||||||
isSelected ? BorderRadius.circular(15) : BorderRadius.zero,
|
topRight: index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
|
||||||
|
bottomRight:
|
||||||
|
index % 3 == 2 ? const Radius.circular(16) : Radius.zero,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Container(
|
||||||
years[index].toString(),
|
alignment: Alignment.center,
|
||||||
style: context.textTheme.titleSmall?.copyWith(
|
decoration: BoxDecoration(
|
||||||
fontSize: 12,
|
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? ColorsManager.whiteColors
|
? ColorsManager.vividBlue.withValues(alpha: 0.7)
|
||||||
: ColorsManager.blackColor.withValues(alpha: 0.8),
|
: const Color(0xFFEDF2F7),
|
||||||
fontWeight: FontWeight.w500,
|
borderRadius:
|
||||||
|
isSelected ? BorderRadius.circular(15) : BorderRadius.zero,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
years[index].toString(),
|
||||||
|
style: context.textTheme.titleSmall?.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
color: isSelected
|
||||||
|
? ColorsManager.whiteColors
|
||||||
|
: ColorsManager.blackColor.withValues(alpha: 0.8),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
|
|
||||||
|
|
||||||
abstract final class EnergyConsumptionByPhasesChartHelper {
|
|
||||||
const EnergyConsumptionByPhasesChartHelper._();
|
|
||||||
|
|
||||||
static const fakeData = <PhasesEnergyConsumption>[
|
|
||||||
PhasesEnergyConsumption(month: 1, phaseA: 200, phaseB: 300, phaseC: 400),
|
|
||||||
PhasesEnergyConsumption(month: 2, phaseA: 300, phaseB: 400, phaseC: 500),
|
|
||||||
PhasesEnergyConsumption(month: 3, phaseA: 400, phaseB: 500, phaseC: 600),
|
|
||||||
PhasesEnergyConsumption(month: 4, phaseA: 100, phaseB: 100, phaseC: 100),
|
|
||||||
PhasesEnergyConsumption(month: 5, phaseA: 300, phaseB: 400, phaseC: 500),
|
|
||||||
PhasesEnergyConsumption(month: 6, phaseA: 300, phaseB: 100, phaseC: 400),
|
|
||||||
PhasesEnergyConsumption(month: 7, phaseA: 300, phaseB: 100, phaseC: 400),
|
|
||||||
PhasesEnergyConsumption(month: 8, phaseA: 500, phaseB: 100, phaseC: 100),
|
|
||||||
PhasesEnergyConsumption(month: 9, phaseA: 500, phaseB: 100, phaseC: 200),
|
|
||||||
PhasesEnergyConsumption(month: 10, phaseA: 100, phaseB: 50, phaseC: 50),
|
|
||||||
PhasesEnergyConsumption(month: 11, phaseA: 600, phaseB: 750, phaseC: 130),
|
|
||||||
PhasesEnergyConsumption(month: 12, phaseA: 100, phaseB: 80, phaseC: 100),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@ -21,12 +21,13 @@ abstract final class EnergyManagementChartsHelper {
|
|||||||
reservedSize: 32,
|
reservedSize: 32,
|
||||||
showTitles: true,
|
showTitles: true,
|
||||||
maxIncluded: true,
|
maxIncluded: true,
|
||||||
|
minIncluded: true,
|
||||||
getTitlesWidget: (value, meta) => Padding(
|
getTitlesWidget: (value, meta) => Padding(
|
||||||
padding: const EdgeInsetsDirectional.only(top: 20.0),
|
padding: const EdgeInsetsDirectional.only(top: 20.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
(value + 1).toString(),
|
value.toString(),
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
color: ColorsManager.greyColor,
|
color: ColorsManager.lightGreyColor,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -36,7 +37,8 @@ abstract final class EnergyManagementChartsHelper {
|
|||||||
leftTitles: AxisTitles(
|
leftTitles: AxisTitles(
|
||||||
sideTitles: SideTitles(
|
sideTitles: SideTitles(
|
||||||
showTitles: true,
|
showTitles: true,
|
||||||
maxIncluded: true,
|
maxIncluded: false,
|
||||||
|
minIncluded: true,
|
||||||
interval: leftTitlesInterval,
|
interval: leftTitlesInterval,
|
||||||
reservedSize: 110,
|
reservedSize: 110,
|
||||||
getTitlesWidget: (value, meta) => Padding(
|
getTitlesWidget: (value, meta) => Padding(
|
||||||
@ -70,7 +72,7 @@ abstract final class EnergyManagementChartsHelper {
|
|||||||
static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) {
|
static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) {
|
||||||
return touchedSpots.map((spot) {
|
return touchedSpots.map((spot) {
|
||||||
return LineTooltipItem(
|
return LineTooltipItem(
|
||||||
getToolTipLabel(spot.x + 1, spot.y),
|
getToolTipLabel(spot.x, spot.y),
|
||||||
const TextStyle(
|
const TextStyle(
|
||||||
color: ColorsManager.textPrimaryColor,
|
color: ColorsManager.textPrimaryColor,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@ -91,31 +93,38 @@ abstract final class EnergyManagementChartsHelper {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static FlBorderData borderData() {
|
|
||||||
return FlBorderData(
|
|
||||||
show: true,
|
|
||||||
border: const Border.symmetric(
|
|
||||||
horizontal: BorderSide(
|
|
||||||
color: ColorsManager.greyColor,
|
|
||||||
style: BorderStyle.solid,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static FlGridData gridData() {
|
static FlGridData gridData() {
|
||||||
return const FlGridData(
|
return FlGridData(
|
||||||
show: true,
|
show: true,
|
||||||
drawVerticalLine: false,
|
drawVerticalLine: false,
|
||||||
drawHorizontalLine: true,
|
drawHorizontalLine: true,
|
||||||
|
horizontalInterval: 250,
|
||||||
|
getDrawingHorizontalLine: (value) {
|
||||||
|
return FlLine(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
strokeWidth: 1,
|
||||||
|
dashArray: value == 0 ? null : [5, 5],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FlBorderData borderData() {
|
||||||
|
return FlBorderData(
|
||||||
|
border: const Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
style: BorderStyle.solid,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
show: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static LineTouchData lineTouchData() {
|
static LineTouchData lineTouchData() {
|
||||||
return LineTouchData(
|
return LineTouchData(
|
||||||
handleBuiltInTouches: true,
|
handleBuiltInTouches: true,
|
||||||
touchSpotThreshold: 2,
|
touchSpotThreshold: 16,
|
||||||
touchTooltipData: EnergyManagementChartsHelper.lineTouchTooltipData(),
|
touchTooltipData: EnergyManagementChartsHelper.lineTouchTooltipData(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_by_phases/energy_consumption_by_phases_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_by_phases/energy_consumption_by_phases_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_per_device/energy_consumption_per_device_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_per_device/energy_consumption_per_device_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
|
||||||
@ -13,13 +16,17 @@ import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_
|
|||||||
abstract final class FetchEnergyManagementDataHelper {
|
abstract final class FetchEnergyManagementDataHelper {
|
||||||
const FetchEnergyManagementDataHelper._();
|
const FetchEnergyManagementDataHelper._();
|
||||||
|
|
||||||
static const String _powerClampId = 'cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa';
|
// static const String _powerClampId = 'cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa';
|
||||||
|
static AnalyticsDevice? getSelectedDevice(BuildContext context) {
|
||||||
|
return context.read<AnalyticsDevicesBloc>().state.selectedDevice;
|
||||||
|
}
|
||||||
|
|
||||||
static void loadEnergyManagementData(
|
static void loadEnergyManagementData(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required String communityId,
|
required String communityId,
|
||||||
required String spaceId,
|
required String spaceId,
|
||||||
DateTime? selectedDate,
|
DateTime? selectedDate,
|
||||||
|
bool shouldFetchAnalyticsDevices = true,
|
||||||
}) {
|
}) {
|
||||||
if (communityId.isEmpty && spaceId.isEmpty) {
|
if (communityId.isEmpty && spaceId.isEmpty) {
|
||||||
clearAllData(context);
|
clearAllData(context);
|
||||||
@ -28,28 +35,46 @@ abstract final class FetchEnergyManagementDataHelper {
|
|||||||
|
|
||||||
final datePickerState = context.read<AnalyticsDatePickerBloc>().state;
|
final datePickerState = context.read<AnalyticsDatePickerBloc>().state;
|
||||||
final selectedDate0 = selectedDate ?? datePickerState.monthlyDate;
|
final selectedDate0 = selectedDate ?? datePickerState.monthlyDate;
|
||||||
|
if (shouldFetchAnalyticsDevices) {
|
||||||
|
loadAnalyticsDevices(
|
||||||
|
context,
|
||||||
|
communityUuid: communityId,
|
||||||
|
spaceUuid: spaceId,
|
||||||
|
selectedDate: selectedDate0,
|
||||||
|
);
|
||||||
|
loadRealtimeDeviceChanges(context);
|
||||||
|
loadPowerClampInfo(context);
|
||||||
|
}
|
||||||
loadTotalEnergyConsumption(
|
loadTotalEnergyConsumption(
|
||||||
context,
|
context,
|
||||||
selectedDate: selectedDate0,
|
selectedDate: selectedDate0,
|
||||||
communityId: communityId,
|
communityId: communityId,
|
||||||
spaceId: spaceId,
|
spaceId: spaceId,
|
||||||
);
|
);
|
||||||
|
final selectedDevice = getSelectedDevice(context);
|
||||||
loadEnergyConsumptionByPhases(context, selectedDate: selectedDate);
|
if (selectedDevice case final AnalyticsDevice device) {
|
||||||
|
loadEnergyConsumptionByPhases(
|
||||||
loadEnergyConsumptionPerDevice(context);
|
context,
|
||||||
loadRealtimeDeviceChanges(context);
|
powerClampUuid: device.uuid,
|
||||||
loadPowerClampInfo(context);
|
selectedDate: selectedDate0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
loadEnergyConsumptionPerDevice(
|
||||||
|
context,
|
||||||
|
communityId: communityId,
|
||||||
|
spaceId: spaceId,
|
||||||
|
selectedDate: selectedDate0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loadEnergyConsumptionByPhases(
|
static void loadEnergyConsumptionByPhases(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
|
required String powerClampUuid,
|
||||||
DateTime? selectedDate,
|
DateTime? selectedDate,
|
||||||
}) {
|
}) {
|
||||||
final param = GetEnergyConsumptionByPhasesParam(
|
final param = GetEnergyConsumptionByPhasesParam(
|
||||||
startDate: selectedDate,
|
date: selectedDate,
|
||||||
spaceId: '',
|
powerClampUuid: powerClampUuid,
|
||||||
);
|
);
|
||||||
context.read<EnergyConsumptionByPhasesBloc>().add(
|
context.read<EnergyConsumptionByPhasesBloc>().add(
|
||||||
LoadEnergyConsumptionByPhasesEvent(param: param),
|
LoadEnergyConsumptionByPhasesEvent(param: param),
|
||||||
@ -72,33 +97,80 @@ abstract final class FetchEnergyManagementDataHelper {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loadEnergyConsumptionPerDevice(BuildContext context) {
|
static void loadEnergyConsumptionPerDevice(
|
||||||
const param = GetEnergyConsumptionPerDeviceParam();
|
BuildContext context, {
|
||||||
|
DateTime? selectedDate,
|
||||||
|
required String communityId,
|
||||||
|
required String spaceId,
|
||||||
|
}) {
|
||||||
|
final param = GetEnergyConsumptionPerDeviceParam(
|
||||||
|
spaceId: spaceId,
|
||||||
|
communityId: communityId,
|
||||||
|
monthDate: selectedDate,
|
||||||
|
);
|
||||||
context.read<EnergyConsumptionPerDeviceBloc>().add(
|
context.read<EnergyConsumptionPerDeviceBloc>().add(
|
||||||
const LoadEnergyConsumptionPerDeviceEvent(param),
|
LoadEnergyConsumptionPerDeviceEvent(param),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loadPowerClampInfo(BuildContext context) {
|
static void loadPowerClampInfo(BuildContext context) {
|
||||||
context.read<PowerClampInfoBloc>().add(
|
final selectedDevice = getSelectedDevice(context);
|
||||||
const LoadPowerClampInfoEvent(_powerClampId),
|
if (selectedDevice case final AnalyticsDevice device) {
|
||||||
|
context.read<PowerClampInfoBloc>().add(
|
||||||
|
LoadPowerClampInfoEvent(device.uuid),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadRealtimeDeviceChanges(
|
||||||
|
BuildContext context, {
|
||||||
|
String? deviceUuid,
|
||||||
|
}) {
|
||||||
|
final selectedDevice = getSelectedDevice(context);
|
||||||
|
|
||||||
|
context.read<RealtimeDeviceChangesBloc>().add(
|
||||||
|
RealtimeDeviceChangesStarted(deviceUuid ?? selectedDevice?.uuid ?? ''),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loadRealtimeDeviceChanges(BuildContext context) {
|
static void loadAnalyticsDevices(
|
||||||
context.read<RealtimeDeviceChangesBloc>().add(
|
BuildContext context, {
|
||||||
const RealtimeDeviceChangesStarted(_powerClampId),
|
required String communityUuid,
|
||||||
|
required String spaceUuid,
|
||||||
|
required DateTime selectedDate,
|
||||||
|
}) {
|
||||||
|
context.read<AnalyticsDevicesBloc>().add(
|
||||||
|
LoadAnalyticsDevicesEvent(
|
||||||
|
onSuccess: (device) {
|
||||||
|
context.read<PowerClampInfoBloc>().add(
|
||||||
|
LoadPowerClampInfoEvent(device.uuid),
|
||||||
|
);
|
||||||
|
loadEnergyConsumptionByPhases(
|
||||||
|
context,
|
||||||
|
powerClampUuid: device.uuid,
|
||||||
|
selectedDate: selectedDate,
|
||||||
|
);
|
||||||
|
context.read<RealtimeDeviceChangesBloc>().add(
|
||||||
|
RealtimeDeviceChangesStarted(device.uuid),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
param: GetAnalyticsDevicesParam(
|
||||||
|
communityUuid: communityUuid,
|
||||||
|
spaceUuid: spaceUuid,
|
||||||
|
deviceTypes: ['PC'],
|
||||||
|
requestType: AnalyticsDeviceRequestType.energyManagement,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void clearAllData(BuildContext context) {
|
static void clearAllData(BuildContext context) {
|
||||||
context.read<RealtimeDeviceChangesBloc>().add(
|
|
||||||
const RealtimeDeviceChangesClosed(),
|
|
||||||
);
|
|
||||||
|
|
||||||
context.read<PowerClampInfoBloc>().add(
|
context.read<PowerClampInfoBloc>().add(
|
||||||
const ClearPowerClampInfoEvent(),
|
const ClearPowerClampInfoEvent(),
|
||||||
);
|
);
|
||||||
|
context.read<RealtimeDeviceChangesBloc>().add(
|
||||||
|
const RealtimeDeviceChangesClosed(),
|
||||||
|
);
|
||||||
|
|
||||||
context.read<EnergyConsumptionPerDeviceBloc>().add(
|
context.read<EnergyConsumptionPerDeviceBloc>().add(
|
||||||
const ClearEnergyConsumptionPerDeviceEvent(),
|
const ClearEnergyConsumptionPerDeviceEvent(),
|
||||||
@ -111,5 +183,6 @@ abstract final class FetchEnergyManagementDataHelper {
|
|||||||
context.read<EnergyConsumptionByPhasesBloc>().add(
|
context.read<EnergyConsumptionByPhasesBloc>().add(
|
||||||
const ClearEnergyConsumptionByPhasesEvent(),
|
const ClearEnergyConsumptionByPhasesEvent(),
|
||||||
);
|
);
|
||||||
|
context.read<AnalyticsDevicesBloc>().add(const ClearAnalyticsDeviceEvent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,108 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class AnalyticsDeviceDropdown extends StatelessWidget {
|
||||||
|
const AnalyticsDeviceDropdown({required this.onChanged, super.key});
|
||||||
|
|
||||||
|
final ValueChanged<AnalyticsDevice> onChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<AnalyticsDevicesBloc, AnalyticsDevicesState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Visibility(
|
||||||
|
visible: state.devices.isNotEmpty,
|
||||||
|
replacement: _buildNoDevicesFound(context),
|
||||||
|
child: _buildDevicesDropdown(context, state),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const _defaultPadding = EdgeInsetsDirectional.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
vertical: 2,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildNoDevicesFound(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: _defaultPadding,
|
||||||
|
child: Text(
|
||||||
|
'no devices found',
|
||||||
|
style: _getTextStyle(context),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDevicesDropdown(BuildContext context, AnalyticsDevicesState state) {
|
||||||
|
final spaceUuid = state.selectedDevice?.spaceUuid;
|
||||||
|
return DropdownButton<AnalyticsDevice?>(
|
||||||
|
value: state.selectedDevice,
|
||||||
|
isDense: true,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
dropdownColor: ColorsManager.whiteColors,
|
||||||
|
underline: const SizedBox.shrink(),
|
||||||
|
icon: const RotatedBox(
|
||||||
|
quarterTurns: 1,
|
||||||
|
child: Icon(Icons.chevron_right, size: 16),
|
||||||
|
),
|
||||||
|
style: _getTextStyle(context),
|
||||||
|
padding: _defaultPadding,
|
||||||
|
selectedItemBuilder: (context) {
|
||||||
|
return state.devices.map((e) => Text(e.name)).toList();
|
||||||
|
},
|
||||||
|
items: state.devices.map((e) {
|
||||||
|
return DropdownMenuItem(
|
||||||
|
value: e,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(e.name),
|
||||||
|
if (spaceUuid != null)
|
||||||
|
FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
child: Text(
|
||||||
|
spaceUuid,
|
||||||
|
style: _getTextStyle(context)?.copyWith(
|
||||||
|
fontSize: 10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value case final AnalyticsDevice device) {
|
||||||
|
context.read<AnalyticsDevicesBloc>().add(
|
||||||
|
SelectAnalyticsDeviceEvent(device),
|
||||||
|
);
|
||||||
|
onChanged.call(device);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextStyle? _getTextStyle(BuildContext context) {
|
||||||
|
return context.textTheme.labelSmall?.copyWith(
|
||||||
|
color: ColorsManager.textPrimaryColor,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 14,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/helpers/get_month_name_from_int.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
|
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
@ -18,7 +18,10 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BarChart(
|
return BarChart(
|
||||||
BarChartData(
|
BarChartData(
|
||||||
gridData: EnergyManagementChartsHelper.gridData(),
|
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
||||||
|
checkToShowHorizontalLine: (value) => true,
|
||||||
|
horizontalInterval: 250,
|
||||||
|
),
|
||||||
borderData: EnergyManagementChartsHelper.borderData(),
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
barTouchData: _barTouchData(context),
|
barTouchData: _barTouchData(context),
|
||||||
titlesData: _titlesData(context),
|
titlesData: _titlesData(context),
|
||||||
@ -31,25 +34,29 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
|
|||||||
barRods: [
|
barRods: [
|
||||||
BarChartRodData(
|
BarChartRodData(
|
||||||
color: ColorsManager.vividBlue.withValues(alpha: 0.1),
|
color: ColorsManager.vividBlue.withValues(alpha: 0.1),
|
||||||
toY: data.phaseA + data.phaseB + data.phaseC,
|
toY: data.energyConsumedA +
|
||||||
|
data.energyConsumedB +
|
||||||
|
data.energyConsumedC,
|
||||||
rodStackItems: [
|
rodStackItems: [
|
||||||
BarChartRodStackItem(
|
BarChartRodStackItem(
|
||||||
0,
|
0,
|
||||||
data.phaseA,
|
data.energyConsumedA,
|
||||||
ColorsManager.vividBlue.withValues(alpha: 0.8),
|
ColorsManager.vividBlue.withValues(alpha: 0.8),
|
||||||
),
|
),
|
||||||
BarChartRodStackItem(
|
BarChartRodStackItem(
|
||||||
data.phaseA,
|
data.energyConsumedA,
|
||||||
data.phaseA + data.phaseB,
|
data.energyConsumedA + data.energyConsumedB,
|
||||||
ColorsManager.vividBlue.withValues(alpha: 0.4),
|
ColorsManager.vividBlue.withValues(alpha: 0.4),
|
||||||
),
|
),
|
||||||
BarChartRodStackItem(
|
BarChartRodStackItem(
|
||||||
data.phaseA + data.phaseB,
|
data.energyConsumedA + data.energyConsumedB,
|
||||||
data.phaseA + data.phaseB + data.phaseC,
|
data.energyConsumedA +
|
||||||
|
data.energyConsumedB +
|
||||||
|
data.energyConsumedC,
|
||||||
ColorsManager.vividBlue.withValues(alpha: 0.15),
|
ColorsManager.vividBlue.withValues(alpha: 0.15),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
width: 16,
|
width: 8,
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: const BorderRadius.only(
|
||||||
topLeft: Radius.circular(8),
|
topLeft: Radius.circular(8),
|
||||||
topRight: Radius.circular(8),
|
topRight: Radius.circular(8),
|
||||||
@ -59,6 +66,7 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
|
duration: Duration.zero,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,18 +99,27 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
|
|||||||
}) {
|
}) {
|
||||||
final data = energyData;
|
final data = energyData;
|
||||||
|
|
||||||
final month = data[group.x.toInt()].month.getMonthName;
|
final date = DateFormat('dd/MM/yyyy').format(data[group.x.toInt()].date);
|
||||||
final phaseA = data[group.x.toInt()].phaseA;
|
final phaseA = data[group.x.toInt()].energyConsumedA;
|
||||||
final phaseB = data[group.x.toInt()].phaseB;
|
final phaseB = data[group.x.toInt()].energyConsumedB;
|
||||||
final phaseC = data[group.x.toInt()].phaseC;
|
final phaseC = data[group.x.toInt()].energyConsumedC;
|
||||||
|
final total = data[group.x.toInt()].energyConsumedKw;
|
||||||
|
|
||||||
return BarTooltipItem(
|
return BarTooltipItem(
|
||||||
'$month\n',
|
'$date\n',
|
||||||
context.textTheme.bodyMedium!.copyWith(
|
context.textTheme.bodyMedium!.copyWith(
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
|
textAlign: TextAlign.start,
|
||||||
children: [
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: 'Total: $total\n',
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: 'Phase A: $phaseA\n',
|
text: 'Phase A: $phaseA\n',
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
@ -144,9 +161,9 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
|
|||||||
sideTitles: SideTitles(
|
sideTitles: SideTitles(
|
||||||
showTitles: true,
|
showTitles: true,
|
||||||
getTitlesWidget: (value, _) {
|
getTitlesWidget: (value, _) {
|
||||||
final month = energyData[value.toInt()].month.getMonthName;
|
final month = DateFormat('d').format(energyData[value.toInt()].date);
|
||||||
return FittedBox(
|
return FittedBox(
|
||||||
alignment: AlignmentDirectional.bottomCenter,
|
alignment: AlignmentDirectional.center,
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
child: RotatedBox(
|
child: RotatedBox(
|
||||||
quarterTurns: 3,
|
quarterTurns: 3,
|
||||||
@ -160,7 +177,7 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
reservedSize: 36,
|
reservedSize: 18,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,11 @@ class EnergyConsumptionPerDeviceChart extends StatelessWidget {
|
|||||||
context,
|
context,
|
||||||
leftTitlesInterval: 250,
|
leftTitlesInterval: 250,
|
||||||
),
|
),
|
||||||
gridData: EnergyManagementChartsHelper.gridData(),
|
|
||||||
|
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
||||||
|
checkToShowHorizontalLine: (value) => true,
|
||||||
|
horizontalInterval: 250,
|
||||||
|
),
|
||||||
borderData: EnergyManagementChartsHelper.borderData(),
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
|
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
|
||||||
lineBarsData: chartData.map((e) {
|
lineBarsData: chartData.map((e) {
|
||||||
@ -33,7 +37,7 @@ class EnergyConsumptionPerDeviceChart extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
duration: Durations.extralong1,
|
duration: Duration.zero,
|
||||||
curve: Curves.easeIn,
|
curve: Curves.easeIn,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_per_device/energy_consumption_per_device_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_per_device/energy_consumption_per_device_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart';
|
||||||
@ -46,6 +47,7 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
|
|||||||
flex: 2,
|
flex: 2,
|
||||||
child: EnergyConsumptionPerDeviceDevicesList(
|
child: EnergyConsumptionPerDeviceDevicesList(
|
||||||
chartData: state.chartData,
|
chartData: state.chartData,
|
||||||
|
devices: context.watch<AnalyticsDevicesBloc>().state.devices,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,10 +1,16 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget {
|
class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget {
|
||||||
const EnergyConsumptionPerDeviceDevicesList({required this.chartData, super.key});
|
const EnergyConsumptionPerDeviceDevicesList({
|
||||||
|
required this.chartData,
|
||||||
|
required this.devices,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<AnalyticsDevice> devices;
|
||||||
final List<DeviceEnergyDataModel> chartData;
|
final List<DeviceEnergyDataModel> chartData;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -16,45 +22,60 @@ class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: chartData.map((e) => _buildDeviceCell(context, e)).toList(),
|
children: devices.map((e) => _buildDeviceCell(context, e)).toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDeviceCell(BuildContext context, DeviceEnergyDataModel device) {
|
Widget _buildDeviceCell(BuildContext context, AnalyticsDevice device) {
|
||||||
return Container(
|
final deviceColor = chartData
|
||||||
height: MediaQuery.sizeOf(context).height * 0.0365,
|
.firstWhere(
|
||||||
padding: const EdgeInsetsDirectional.symmetric(
|
(element) => element.deviceId == device.uuid,
|
||||||
vertical: 8,
|
orElse: () => const DeviceEnergyDataModel(
|
||||||
horizontal: 12,
|
energy: [],
|
||||||
),
|
deviceName: '',
|
||||||
decoration: BoxDecoration(
|
deviceId: '',
|
||||||
borderRadius: BorderRadiusDirectional.circular(8),
|
color: Colors.red,
|
||||||
border: Border.all(
|
),
|
||||||
color: ColorsManager.greyColor,
|
)
|
||||||
width: 1,
|
.color;
|
||||||
|
|
||||||
|
return Tooltip(
|
||||||
|
message: '${device.name}\n${device.productDevice?.uuid ?? ''}',
|
||||||
|
child: Container(
|
||||||
|
height: MediaQuery.sizeOf(context).height * 0.0365,
|
||||||
|
padding: const EdgeInsetsDirectional.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
horizontal: 12,
|
||||||
),
|
),
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
child: FittedBox(
|
borderRadius: BorderRadiusDirectional.circular(8),
|
||||||
fit: BoxFit.scaleDown,
|
border: Border.all(
|
||||||
alignment: Alignment.center,
|
color: ColorsManager.greyColor,
|
||||||
child: Row(
|
width: 1,
|
||||||
spacing: 6,
|
),
|
||||||
children: [
|
),
|
||||||
CircleAvatar(
|
child: FittedBox(
|
||||||
radius: 4,
|
fit: BoxFit.scaleDown,
|
||||||
backgroundColor: device.color,
|
alignment: Alignment.center,
|
||||||
),
|
child: Row(
|
||||||
Text(
|
spacing: 6,
|
||||||
device.deviceName,
|
children: [
|
||||||
textAlign: TextAlign.center,
|
CircleAvatar(
|
||||||
style: const TextStyle(
|
radius: 4,
|
||||||
color: ColorsManager.blackColor,
|
backgroundColor: deviceColor,
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
),
|
||||||
),
|
Text(
|
||||||
],
|
device.name,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,55 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
|
||||||
|
|
||||||
class PowerClampEnergyDataDeviceDropdown extends StatelessWidget {
|
|
||||||
const PowerClampEnergyDataDeviceDropdown({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
border: Border.all(
|
|
||||||
color: ColorsManager.greyColor,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: DropdownButton<String>(
|
|
||||||
value: 'Device 1',
|
|
||||||
isDense: true,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
dropdownColor: ColorsManager.whiteColors,
|
|
||||||
underline: const SizedBox.shrink(),
|
|
||||||
icon: const RotatedBox(
|
|
||||||
quarterTurns: 1,
|
|
||||||
child: Icon(Icons.chevron_right, size: 16),
|
|
||||||
),
|
|
||||||
style: context.textTheme.labelSmall?.copyWith(
|
|
||||||
color: ColorsManager.textPrimaryColor,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
padding: const EdgeInsetsDirectional.symmetric(
|
|
||||||
horizontal: 20,
|
|
||||||
vertical: 2,
|
|
||||||
),
|
|
||||||
items: [
|
|
||||||
for (var i = 1; i < 10; i++)
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: 'Device $i',
|
|
||||||
child: Text(
|
|
||||||
'Device $i',
|
|
||||||
style: context.textTheme.labelSmall?.copyWith(
|
|
||||||
color: ColorsManager.textPrimaryColor,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onChanged: (value) {},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,10 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart';
|
import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart_box.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart_box.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_device_dropdown.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
||||||
@ -50,7 +53,8 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
SelectableText(
|
SelectableText(
|
||||||
state.powerClampModel?.productUuid ?? 'N/A',
|
context.watch<AnalyticsDevicesBloc>().state.selectedDevice?.uuid ??
|
||||||
|
'N/A',
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
@ -107,7 +111,7 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 3,
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
@ -122,11 +126,25 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
const Expanded(
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
alignment: AlignmentDirectional.centerEnd,
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
child: PowerClampEnergyDataDeviceDropdown(),
|
child: AnalyticsDeviceDropdown(
|
||||||
|
onChanged: (value) {
|
||||||
|
FetchEnergyManagementDataHelper.loadEnergyConsumptionByPhases(
|
||||||
|
context,
|
||||||
|
powerClampUuid: value.uuid,
|
||||||
|
selectedDate:
|
||||||
|
context.read<AnalyticsDatePickerBloc>().state.monthlyDate,
|
||||||
|
);
|
||||||
|
FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges(
|
||||||
|
context,
|
||||||
|
deviceUuid: value.uuid,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -48,6 +48,9 @@ class PowerClampEnergyStatusWidget extends StatelessWidget {
|
|||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
|
softWrap: true,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
trailing: Text.rich(
|
trailing: Text.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
|
|||||||
@ -4,15 +4,6 @@ import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
|||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
// energy_consumption_chart will return id, name and consumption
|
|
||||||
const phasesJson = {
|
|
||||||
"1": {
|
|
||||||
"phaseOne": 1000,
|
|
||||||
"phaseTwo": 2000,
|
|
||||||
"phaseThree": 3000,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class TotalEnergyConsumptionChart extends StatelessWidget {
|
class TotalEnergyConsumptionChart extends StatelessWidget {
|
||||||
const TotalEnergyConsumptionChart({required this.chartData, super.key});
|
const TotalEnergyConsumptionChart({required this.chartData, super.key});
|
||||||
|
|
||||||
@ -23,13 +14,19 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
|
|||||||
return Expanded(
|
return Expanded(
|
||||||
child: LineChart(
|
child: LineChart(
|
||||||
LineChartData(
|
LineChartData(
|
||||||
titlesData: EnergyManagementChartsHelper.titlesData(context),
|
titlesData: EnergyManagementChartsHelper.titlesData(
|
||||||
gridData: EnergyManagementChartsHelper.gridData(),
|
context,
|
||||||
|
leftTitlesInterval: 250,
|
||||||
|
),
|
||||||
|
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
||||||
|
checkToShowHorizontalLine: (value) => true,
|
||||||
|
horizontalInterval: 250,
|
||||||
|
),
|
||||||
borderData: EnergyManagementChartsHelper.borderData(),
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
|
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
|
||||||
lineBarsData: _lineBarsData,
|
lineBarsData: _lineBarsData,
|
||||||
),
|
),
|
||||||
duration: Durations.extralong1,
|
duration: Duration.zero,
|
||||||
curve: Curves.easeIn,
|
curve: Curves.easeIn,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -46,7 +43,7 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
|
|||||||
.entries
|
.entries
|
||||||
.map(
|
.map(
|
||||||
(entry) => FlSpot(
|
(entry) => FlSpot(
|
||||||
entry.key.toDouble(),
|
entry.value.date.day.toDouble(),
|
||||||
entry.value.value,
|
entry.value.value,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart';
|
||||||
|
|
||||||
@ -17,36 +20,77 @@ abstract final class FetchOccupancyDataHelper {
|
|||||||
}) {
|
}) {
|
||||||
if (communityId.isEmpty && spaceId.isEmpty) {
|
if (communityId.isEmpty && spaceId.isEmpty) {
|
||||||
clearAllData(context);
|
clearAllData(context);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final datePickerState = context.read<AnalyticsDatePickerBloc>().state;
|
final datePickerState = context.read<AnalyticsDatePickerBloc>().state;
|
||||||
|
|
||||||
|
loadAnalyticsDevices(context, communityUuid: communityId, spaceUuid: spaceId);
|
||||||
|
final selectedDevice = context.read<AnalyticsDevicesBloc>().state.selectedDevice;
|
||||||
|
|
||||||
|
loadOccupancyChartData(
|
||||||
|
context,
|
||||||
|
spaceUuid: spaceId,
|
||||||
|
date: datePickerState.monthlyDate,
|
||||||
|
);
|
||||||
|
loadHeatMapData(context, spaceUuid: spaceId, year: datePickerState.yearlyDate);
|
||||||
|
|
||||||
|
if (selectedDevice case final AnalyticsDevice device) {
|
||||||
|
context.read<RealtimeDeviceChangesBloc>()
|
||||||
|
..add(const RealtimeDeviceChangesClosed())
|
||||||
|
..add(
|
||||||
|
RealtimeDeviceChangesStarted(device.uuid),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadHeatMapData(
|
||||||
|
BuildContext context, {
|
||||||
|
required String spaceUuid,
|
||||||
|
required DateTime year,
|
||||||
|
}) {
|
||||||
|
context.read<OccupancyHeatMapBloc>().add(
|
||||||
|
LoadOccupancyHeatMapEvent(
|
||||||
|
GetOccupancyHeatMapParam(spaceUuid: spaceUuid, year: year),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadOccupancyChartData(
|
||||||
|
BuildContext context, {
|
||||||
|
required String spaceUuid,
|
||||||
|
required DateTime date,
|
||||||
|
}) {
|
||||||
context.read<OccupancyBloc>().add(
|
context.read<OccupancyBloc>().add(
|
||||||
LoadOccupancyEvent(
|
LoadOccupancyEvent(
|
||||||
GetOccupancyParam(
|
GetOccupancyParam(
|
||||||
monthDate:
|
monthDate: '${date.year}-${date.month.toString().padLeft(2, '0')}',
|
||||||
'${datePickerState.monthlyDate.year}-${datePickerState.monthlyDate.month}',
|
spaceUuid: spaceUuid,
|
||||||
spaceUuid: spaceId,
|
|
||||||
communityUuid: communityId,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
context.read<OccupancyHeatMapBloc>().add(
|
static void loadAnalyticsDevices(
|
||||||
LoadOccupancyHeatMapEvent(
|
BuildContext context, {
|
||||||
GetOccupancyHeatMapParam(
|
required String communityUuid,
|
||||||
spaceId: spaceId,
|
required String spaceUuid,
|
||||||
communityId: communityId,
|
}) {
|
||||||
year: datePickerState.yearlyDate,
|
context.read<AnalyticsDevicesBloc>().add(
|
||||||
|
LoadAnalyticsDevicesEvent(
|
||||||
|
param: GetAnalyticsDevicesParam(
|
||||||
|
communityUuid: communityUuid,
|
||||||
|
spaceUuid: spaceUuid,
|
||||||
|
deviceTypes: ['WPS', 'CPS'],
|
||||||
|
requestType: AnalyticsDeviceRequestType.occupancy,
|
||||||
),
|
),
|
||||||
|
onSuccess: (device) {
|
||||||
|
context.read<RealtimeDeviceChangesBloc>()
|
||||||
|
..add(const RealtimeDeviceChangesClosed())
|
||||||
|
..add(RealtimeDeviceChangesStarted(device.uuid));
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
context.read<RealtimeDeviceChangesBloc>()
|
|
||||||
..add(const RealtimeDeviceChangesClosed())
|
|
||||||
..add(
|
|
||||||
const RealtimeDeviceChangesStarted('14fe6e7e-47af-4a07-ae0a-7c4a26ef8135'),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void clearAllData(BuildContext context) {
|
static void clearAllData(BuildContext context) {
|
||||||
@ -59,5 +103,9 @@ abstract final class FetchOccupancyDataHelper {
|
|||||||
context.read<RealtimeDeviceChangesBloc>().add(
|
context.read<RealtimeDeviceChangesBloc>().add(
|
||||||
const RealtimeDeviceChangesClosed(),
|
const RealtimeDeviceChangesClosed(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
context.read<AnalyticsDevicesBloc>().add(
|
||||||
|
const ClearAnalyticsDeviceEvent(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,9 +20,9 @@ class AnalyticsOccupancyView extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
spacing: 32,
|
spacing: 32,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: height * 0.45, child: const OccupancyEndSideBar()),
|
SizedBox(height: height * 0.46, child: const OccupancyEndSideBar()),
|
||||||
SizedBox(height: height * 0.5, child: const OccupancyChartBox()),
|
SizedBox(height: height * 0.5, child: const OccupancyChartBox()),
|
||||||
SizedBox(height: height * 0.5, child: const Placeholder()),
|
SizedBox(height: height * 0.5, child: const OccupancyHeatMapBox()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,54 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class HeatMapTooltip extends StatelessWidget {
|
||||||
|
const HeatMapTooltip({
|
||||||
|
required this.date,
|
||||||
|
required this.value,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DateTime date;
|
||||||
|
final int value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.topStart,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.grey700,
|
||||||
|
borderRadius: BorderRadius.circular(3),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
DateFormat('MMM d, yyyy').format(date),
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 2, thickness: 1),
|
||||||
|
Text(
|
||||||
|
'$value Occupants',
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,108 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/heat_map_tooltip.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_painter.dart';
|
||||||
|
|
||||||
|
class InteractiveHeatMap extends StatefulWidget {
|
||||||
|
const InteractiveHeatMap({
|
||||||
|
required this.items,
|
||||||
|
required this.maxValue,
|
||||||
|
required this.cellSize,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<OccupancyPaintItem> items;
|
||||||
|
final int maxValue;
|
||||||
|
final double cellSize;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<InteractiveHeatMap> createState() => _InteractiveHeatMapState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InteractiveHeatMapState extends State<InteractiveHeatMap> {
|
||||||
|
OccupancyPaintItem? _hoveredItem;
|
||||||
|
OverlayEntry? _overlayEntry;
|
||||||
|
final LayerLink _layerLink = LayerLink();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_removeOverlay();
|
||||||
|
_overlayEntry?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _removeOverlay() {
|
||||||
|
_overlayEntry?.remove();
|
||||||
|
_overlayEntry = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showTooltip(OccupancyPaintItem item, Offset localPosition) {
|
||||||
|
_removeOverlay();
|
||||||
|
|
||||||
|
final column = item.index ~/ 7;
|
||||||
|
final row = item.index % 7;
|
||||||
|
final x = column * widget.cellSize;
|
||||||
|
final y = row * widget.cellSize;
|
||||||
|
|
||||||
|
_overlayEntry = OverlayEntry(
|
||||||
|
builder: (context) => Positioned(
|
||||||
|
child: CompositedTransformFollower(
|
||||||
|
link: _layerLink,
|
||||||
|
offset: Offset(x + widget.cellSize, y),
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Transform.translate(
|
||||||
|
offset: Offset(-(widget.cellSize * 2.5), -50),
|
||||||
|
child: HeatMapTooltip(date: item.date, value: item.value),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Overlay.of(context).insert(_overlayEntry!);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CompositedTransformTarget(
|
||||||
|
link: _layerLink,
|
||||||
|
child: MouseRegion(
|
||||||
|
onHover: (event) {
|
||||||
|
final column = event.localPosition.dx ~/ widget.cellSize;
|
||||||
|
final row = event.localPosition.dy ~/ widget.cellSize;
|
||||||
|
final index = column * 7 + row;
|
||||||
|
|
||||||
|
if (index >= 0 && index < widget.items.length) {
|
||||||
|
final item = widget.items[index];
|
||||||
|
if (_hoveredItem != item) {
|
||||||
|
setState(() => _hoveredItem = item);
|
||||||
|
_showTooltip(item, event.localPosition);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_removeOverlay();
|
||||||
|
setState(() => _hoveredItem = null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onExit: (_) {
|
||||||
|
_removeOverlay();
|
||||||
|
setState(() => _hoveredItem = null);
|
||||||
|
},
|
||||||
|
child: CustomPaint(
|
||||||
|
isComplex: true,
|
||||||
|
size: _painterSize,
|
||||||
|
painter: OccupancyPainter(
|
||||||
|
items: widget.items,
|
||||||
|
maxValue: widget.maxValue,
|
||||||
|
hoveredItem: _hoveredItem,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Size get _painterSize {
|
||||||
|
final height = 7 * widget.cellSize;
|
||||||
|
final width = widget.items.length ~/ 7 * widget.cellSize;
|
||||||
|
return Size(width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,30 +16,38 @@ class OccupancyChart extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BarChart(
|
return BarChart(
|
||||||
BarChartData(
|
BarChartData(
|
||||||
maxY: 1.0,
|
maxY: 100.0,
|
||||||
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
||||||
checkToShowHorizontalLine: (value) => true,
|
checkToShowHorizontalLine: (value) => true,
|
||||||
horizontalInterval: 0.25,
|
horizontalInterval: 20,
|
||||||
),
|
),
|
||||||
borderData: EnergyManagementChartsHelper.borderData(),
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
barTouchData: _barTouchData(context),
|
barTouchData: _barTouchData(context),
|
||||||
titlesData: _titlesData(context),
|
titlesData: _titlesData(context).copyWith(
|
||||||
|
leftTitles: _titlesData(context).leftTitles.copyWith(
|
||||||
|
sideTitles: _titlesData(context).leftTitles.sideTitles.copyWith(
|
||||||
|
maxIncluded: true,
|
||||||
|
minIncluded: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
barGroups: List.generate(chartData.length, (index) {
|
barGroups: List.generate(chartData.length, (index) {
|
||||||
final actual = chartData[index];
|
final actual = chartData[index];
|
||||||
|
final occupancyValue = double.parse(actual.occupancy);
|
||||||
return BarChartGroupData(
|
return BarChartGroupData(
|
||||||
x: index,
|
x: index,
|
||||||
barsSpace: 0,
|
barsSpace: 0,
|
||||||
groupVertically: true,
|
groupVertically: true,
|
||||||
barRods: [
|
barRods: [
|
||||||
BarChartRodData(
|
BarChartRodData(
|
||||||
toY: 1.0,
|
toY: 100.0,
|
||||||
fromY: double.parse(actual.occupancy) + 0.025,
|
fromY: occupancyValue == 0 ? occupancyValue : occupancyValue + 2.5,
|
||||||
color: ColorsManager.graysColor,
|
color: ColorsManager.graysColor,
|
||||||
width: _chartWidth,
|
width: _chartWidth,
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
BarChartRodData(
|
BarChartRodData(
|
||||||
toY: double.parse(actual.occupancy),
|
toY: occupancyValue,
|
||||||
color: ColorsManager.vividBlue.withValues(alpha: 0.8),
|
color: ColorsManager.vividBlue.withValues(alpha: 0.8),
|
||||||
width: _chartWidth,
|
width: _chartWidth,
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
@ -81,7 +89,7 @@ class OccupancyChart extends StatelessWidget {
|
|||||||
final data = chartData;
|
final data = chartData;
|
||||||
|
|
||||||
final occupancyValue = double.parse(data[group.x.toInt()].occupancy);
|
final occupancyValue = double.parse(data[group.x.toInt()].occupancy);
|
||||||
final percentage = '${(occupancyValue * 100).toStringAsFixed(0)}%';
|
final percentage = '${(occupancyValue).toStringAsFixed(0)}%';
|
||||||
|
|
||||||
return BarTooltipItem(
|
return BarTooltipItem(
|
||||||
percentage,
|
percentage,
|
||||||
@ -101,14 +109,14 @@ class OccupancyChart extends StatelessWidget {
|
|||||||
final leftTitles = titlesData.leftTitles.copyWith(
|
final leftTitles = titlesData.leftTitles.copyWith(
|
||||||
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
||||||
reservedSize: 70,
|
reservedSize: 70,
|
||||||
interval: 0.25,
|
interval: 20,
|
||||||
getTitlesWidget: (value, meta) => Padding(
|
getTitlesWidget: (value, meta) => Padding(
|
||||||
padding: const EdgeInsetsDirectional.only(end: 12),
|
padding: const EdgeInsetsDirectional.only(end: 12),
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
child: Text(
|
child: Text(
|
||||||
'${(value * 100).toStringAsFixed(0)}%',
|
'${(value).toStringAsFixed(0)}%',
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: ColorsManager.greyColor,
|
color: ColorsManager.greyColor,
|
||||||
|
|||||||
@ -47,11 +47,14 @@ class OccupancyChartBox extends StatelessWidget {
|
|||||||
context.read<AnalyticsDatePickerBloc>().add(
|
context.read<AnalyticsDatePickerBloc>().add(
|
||||||
UpdateAnalyticsDatePickerEvent(montlyDate: value),
|
UpdateAnalyticsDatePickerEvent(montlyDate: value),
|
||||||
);
|
);
|
||||||
FetchOccupancyDataHelper.loadOccupancyData(
|
if (spaceTreeState.selectedSpaces.isNotEmpty) {
|
||||||
context,
|
FetchOccupancyDataHelper.loadOccupancyChartData(
|
||||||
communityId: spaceTreeState.selectedCommunities.firstOrNull ?? '',
|
context,
|
||||||
spaceId: spaceTreeState.selectedSpaces.firstOrNull ?? '',
|
spaceUuid:
|
||||||
);
|
spaceTreeState.selectedSpaces.firstOrNull ?? '',
|
||||||
|
date: value,
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
selectedDate: context
|
selectedDate: context
|
||||||
.watch<AnalyticsDatePickerBloc>()
|
.watch<AnalyticsDatePickerBloc>()
|
||||||
|
|||||||
@ -1,15 +1,16 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart';
|
import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_device_dropdown.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
import 'package:syncrow_web/utils/style.dart';
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
|
||||||
|
|
||||||
class OccupancyEndSideBar extends StatelessWidget {
|
class OccupancyEndSideBar extends StatelessWidget {
|
||||||
const OccupancyEndSideBar({super.key});
|
const OccupancyEndSideBar({super.key});
|
||||||
@ -37,7 +38,8 @@ class OccupancyEndSideBar extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
SelectableText(
|
SelectableText(
|
||||||
(const Uuid().v4()),
|
context.watch<AnalyticsDevicesBloc>().state.selectedDevice?.uuid ??
|
||||||
|
'N/A',
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
@ -105,7 +107,7 @@ class OccupancyEndSideBar extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 3,
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
alignment: AlignmentDirectional.centerStart,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
@ -120,11 +122,18 @@ class OccupancyEndSideBar extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
const Expanded(
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
alignment: AlignmentDirectional.centerEnd,
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
child: PowerClampEnergyDataDeviceDropdown(),
|
child: AnalyticsDeviceDropdown(
|
||||||
|
onChanged: (value) =>
|
||||||
|
FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges(
|
||||||
|
context,
|
||||||
|
deviceUuid: value.uuid,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'dart:math' as math show max;
|
import 'dart:math' as math show max;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/interactive_heat_map.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_days.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_days.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_gradient.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_gradient.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_months.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_months.dart';
|
||||||
@ -15,7 +16,7 @@ class OccupancyHeatMap extends StatelessWidget {
|
|||||||
static const _totalWeeks = 53;
|
static const _totalWeeks = 53;
|
||||||
|
|
||||||
int get _maxValue => heatMapData.isNotEmpty
|
int get _maxValue => heatMapData.isNotEmpty
|
||||||
? heatMapData.keys.map((key) => heatMapData[key]!).reduce(math.max)
|
? heatMapData.keys.map((key) => heatMapData[key] ?? 0).reduce(math.max)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
DateTime _getStartingDate() {
|
DateTime _getStartingDate() {
|
||||||
@ -28,7 +29,7 @@ class OccupancyHeatMap extends StatelessWidget {
|
|||||||
return List.generate(_totalWeeks * 7, (index) {
|
return List.generate(_totalWeeks * 7, (index) {
|
||||||
final date = startDate.add(Duration(days: index));
|
final date = startDate.add(Duration(days: index));
|
||||||
final value = heatMapData[date] ?? 0;
|
final value = heatMapData[date] ?? 0;
|
||||||
return OccupancyPaintItem(index: index, value: value);
|
return OccupancyPaintItem(index: index, value: value, date: date);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,15 +59,13 @@ class OccupancyHeatMap extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const OccupancyHeatMapDays(cellSize: _cellSize),
|
const OccupancyHeatMapDays(cellSize: _cellSize),
|
||||||
CustomPaint(
|
SizedBox(
|
||||||
size: const Size(_totalWeeks * _cellSize, 7 * _cellSize),
|
width: _totalWeeks * _cellSize,
|
||||||
child: CustomPaint(
|
height: 7 * _cellSize,
|
||||||
isComplex: true,
|
child: InteractiveHeatMap(
|
||||||
size: const Size(_totalWeeks * _cellSize, 7 * _cellSize),
|
items: paintItems,
|
||||||
painter: OccupancyPainter(
|
maxValue: _maxValue,
|
||||||
items: paintItems,
|
cellSize: _cellSize,
|
||||||
maxValue: _maxValue,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -47,12 +47,14 @@ class OccupancyHeatMapBox extends StatelessWidget {
|
|||||||
context.read<AnalyticsDatePickerBloc>().add(
|
context.read<AnalyticsDatePickerBloc>().add(
|
||||||
UpdateAnalyticsDatePickerEvent(yearlyDate: value),
|
UpdateAnalyticsDatePickerEvent(yearlyDate: value),
|
||||||
);
|
);
|
||||||
FetchOccupancyDataHelper.loadOccupancyData(
|
if (spaceTreeState.selectedSpaces.isNotEmpty) {
|
||||||
context,
|
FetchOccupancyDataHelper.loadHeatMapData(
|
||||||
communityId:
|
context,
|
||||||
spaceTreeState.selectedCommunities.firstOrNull ?? '',
|
spaceUuid:
|
||||||
spaceId: spaceTreeState.selectedSpaces.firstOrNull ?? '',
|
spaceTreeState.selectedSpaces.firstOrNull ?? '',
|
||||||
);
|
year: value,
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
datePickerType: DatePickerType.year,
|
datePickerType: DatePickerType.year,
|
||||||
selectedDate: context
|
selectedDate: context
|
||||||
@ -68,7 +70,10 @@ class OccupancyHeatMapBox extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: OccupancyHeatMap(
|
child: OccupancyHeatMap(
|
||||||
heatMapData: state.heatMapData.asMap().map(
|
heatMapData: state.heatMapData.asMap().map(
|
||||||
(_, value) => MapEntry(value.date, value.occupancy),
|
(_, value) => MapEntry(
|
||||||
|
value.eventDate,
|
||||||
|
value.countTotalPresenceDetected,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -4,18 +4,25 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
|||||||
class OccupancyPaintItem {
|
class OccupancyPaintItem {
|
||||||
final int index;
|
final int index;
|
||||||
final int value;
|
final int value;
|
||||||
|
final DateTime date;
|
||||||
|
|
||||||
const OccupancyPaintItem({required this.index, required this.value});
|
const OccupancyPaintItem({
|
||||||
|
required this.index,
|
||||||
|
required this.value,
|
||||||
|
required this.date,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class OccupancyPainter extends CustomPainter {
|
class OccupancyPainter extends CustomPainter {
|
||||||
OccupancyPainter({
|
OccupancyPainter({
|
||||||
required this.items,
|
required this.items,
|
||||||
required this.maxValue,
|
required this.maxValue,
|
||||||
|
this.hoveredItem,
|
||||||
});
|
});
|
||||||
|
|
||||||
final List<OccupancyPaintItem> items;
|
final List<OccupancyPaintItem> items;
|
||||||
final int maxValue;
|
final int maxValue;
|
||||||
|
final OccupancyPaintItem? hoveredItem;
|
||||||
|
|
||||||
static const double cellSize = 16.0;
|
static const double cellSize = 16.0;
|
||||||
|
|
||||||
@ -25,6 +32,10 @@ class OccupancyPainter extends CustomPainter {
|
|||||||
final Paint borderPaint = Paint()
|
final Paint borderPaint = Paint()
|
||||||
..color = ColorsManager.grayBorder.withValues(alpha: 0.4)
|
..color = ColorsManager.grayBorder.withValues(alpha: 0.4)
|
||||||
..style = PaintingStyle.stroke;
|
..style = PaintingStyle.stroke;
|
||||||
|
final Paint hoveredBorderPaint = Paint()
|
||||||
|
..color = Colors.black
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 1.5;
|
||||||
|
|
||||||
for (final item in items) {
|
for (final item in items) {
|
||||||
final column = item.index ~/ 7;
|
final column = item.index ~/ 7;
|
||||||
@ -37,22 +48,27 @@ class OccupancyPainter extends CustomPainter {
|
|||||||
final rect = Rect.fromLTWH(x, y, cellSize, cellSize);
|
final rect = Rect.fromLTWH(x, y, cellSize, cellSize);
|
||||||
canvas.drawRect(rect, fillPaint);
|
canvas.drawRect(rect, fillPaint);
|
||||||
|
|
||||||
_drawDashedLine(
|
// Highlight the hovered item
|
||||||
canvas,
|
if (hoveredItem != null && hoveredItem!.index == item.index) {
|
||||||
Offset(x, y),
|
canvas.drawRect(rect, hoveredBorderPaint);
|
||||||
Offset(x + cellSize, y),
|
} else {
|
||||||
borderPaint,
|
_drawDashedLine(
|
||||||
);
|
canvas,
|
||||||
_drawDashedLine(
|
Offset(x, y),
|
||||||
canvas,
|
Offset(x + cellSize, y),
|
||||||
Offset(x, y + cellSize),
|
borderPaint,
|
||||||
Offset(x + cellSize, y + cellSize),
|
);
|
||||||
borderPaint,
|
_drawDashedLine(
|
||||||
);
|
canvas,
|
||||||
|
Offset(x, y + cellSize),
|
||||||
|
Offset(x + cellSize, y + cellSize),
|
||||||
|
borderPaint,
|
||||||
|
);
|
||||||
|
|
||||||
canvas.drawLine(Offset(x, y), Offset(x, y + cellSize), borderPaint);
|
canvas.drawLine(Offset(x, y), Offset(x, y + cellSize), borderPaint);
|
||||||
canvas.drawLine(
|
canvas.drawLine(Offset(x + cellSize, y), Offset(x + cellSize, y + cellSize),
|
||||||
Offset(x + cellSize, y), Offset(x + cellSize, y + cellSize), borderPaint);
|
borderPaint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,5 +96,6 @@ class OccupancyPainter extends CustomPainter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
bool shouldRepaint(covariant OccupancyPainter oldDelegate) =>
|
||||||
|
oldDelegate.hoveredItem != hoveredItem;
|
||||||
}
|
}
|
||||||
|
|||||||
22
lib/pages/analytics/params/get_analytics_devices_param.dart
Normal file
22
lib/pages/analytics/params/get_analytics_devices_param.dart
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
enum AnalyticsDeviceRequestType { energyManagement, occupancy }
|
||||||
|
|
||||||
|
class GetAnalyticsDevicesParam {
|
||||||
|
final String? spaceUuid;
|
||||||
|
final List<String> deviceTypes;
|
||||||
|
final String? communityUuid;
|
||||||
|
final AnalyticsDeviceRequestType requestType;
|
||||||
|
|
||||||
|
const GetAnalyticsDevicesParam({
|
||||||
|
required this.requestType,
|
||||||
|
required this.spaceUuid,
|
||||||
|
required this.deviceTypes,
|
||||||
|
required this.communityUuid,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return <String, dynamic>{
|
||||||
|
if (spaceUuid != null) 'spaceUuid': spaceUuid,
|
||||||
|
if (communityUuid != null) 'communityUuid': communityUuid,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,24 +1,20 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
class GetEnergyConsumptionByPhasesParam extends Equatable {
|
class GetEnergyConsumptionByPhasesParam extends Equatable {
|
||||||
final DateTime? startDate;
|
final String powerClampUuid;
|
||||||
final DateTime? endDate;
|
final DateTime? date;
|
||||||
final String? spaceId;
|
|
||||||
|
|
||||||
const GetEnergyConsumptionByPhasesParam({
|
const GetEnergyConsumptionByPhasesParam({
|
||||||
this.startDate,
|
required this.powerClampUuid,
|
||||||
this.endDate,
|
this.date,
|
||||||
this.spaceId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {
|
||||||
'startDate': startDate?.toIso8601String(),
|
'monthDate': '${date?.year}-${date?.month.toString().padLeft(2, '0')}',
|
||||||
'endDate': endDate?.toIso8601String(),
|
|
||||||
'spaceId': spaceId,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [startDate, endDate, spaceId];
|
List<Object?> get props => [powerClampUuid, date];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,19 @@
|
|||||||
class GetEnergyConsumptionPerDeviceParam {
|
class GetEnergyConsumptionPerDeviceParam {
|
||||||
const GetEnergyConsumptionPerDeviceParam();
|
const GetEnergyConsumptionPerDeviceParam({
|
||||||
|
this.monthDate,
|
||||||
|
this.spaceId,
|
||||||
|
this.communityId,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DateTime? monthDate;
|
||||||
|
final String? spaceId;
|
||||||
|
final String? communityId;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'monthDate':
|
||||||
|
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
|
||||||
|
if (spaceId == null || spaceId == null) 'spaceUuid': spaceId,
|
||||||
|
'communityUuid': communityId,
|
||||||
|
'groupByDevice': true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,13 @@
|
|||||||
class GetOccupancyHeatMapParam {
|
class GetOccupancyHeatMapParam {
|
||||||
final DateTime year;
|
final DateTime year;
|
||||||
final String communityId;
|
final String spaceUuid;
|
||||||
final String spaceId;
|
|
||||||
|
|
||||||
const GetOccupancyHeatMapParam({
|
const GetOccupancyHeatMapParam({
|
||||||
required this.year,
|
required this.year,
|
||||||
required this.communityId,
|
required this.spaceUuid,
|
||||||
required this.spaceId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {'year': year.year};
|
||||||
'year': year.toIso8601String(),
|
|
||||||
'communityId': communityId,
|
|
||||||
'spaceId': spaceId,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,11 @@
|
|||||||
class GetOccupancyParam {
|
class GetOccupancyParam {
|
||||||
final String monthDate;
|
final String monthDate;
|
||||||
final String? spaceUuid;
|
final String? spaceUuid;
|
||||||
final String communityUuid;
|
|
||||||
|
|
||||||
GetOccupancyParam({
|
GetOccupancyParam({
|
||||||
required this.monthDate,
|
required this.monthDate,
|
||||||
required this.spaceUuid,
|
required this.spaceUuid,
|
||||||
required this.communityUuid,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() => {'monthDate': monthDate};
|
||||||
return {
|
|
||||||
'monthDate': monthDate,
|
|
||||||
'spaceUuid': spaceUuid,
|
|
||||||
'communityUuid': communityUuid,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ class GetTotalEnergyConsumptionParam {
|
|||||||
return {
|
return {
|
||||||
'monthDate':
|
'monthDate':
|
||||||
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
|
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
|
||||||
if (communityId == null || communityId!.isEmpty) 'spaceUuid': spaceId,
|
if (spaceId == null || spaceId == null) 'spaceUuid': spaceId,
|
||||||
'communityUuid': communityId,
|
'communityUuid': communityId,
|
||||||
'groupByDevice': false,
|
'groupByDevice': false,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
|
||||||
|
|
||||||
|
abstract interface class AnalyticsDevicesService {
|
||||||
|
Future<List<AnalyticsDevice>> getDevices(GetAnalyticsDevicesParam param);
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart';
|
||||||
|
|
||||||
|
class AnalyticsDevicesServiceDelegate implements AnalyticsDevicesService {
|
||||||
|
const AnalyticsDevicesServiceDelegate(
|
||||||
|
this._occupancyService,
|
||||||
|
this._energyManagementService,
|
||||||
|
);
|
||||||
|
|
||||||
|
final AnalyticsDevicesService _occupancyService;
|
||||||
|
final AnalyticsDevicesService _energyManagementService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<AnalyticsDevice>> getDevices(
|
||||||
|
GetAnalyticsDevicesParam param,
|
||||||
|
) {
|
||||||
|
return switch (param.requestType) {
|
||||||
|
AnalyticsDeviceRequestType.occupancy => _occupancyService.getDevices(param),
|
||||||
|
AnalyticsDeviceRequestType.energyManagement =>
|
||||||
|
_energyManagementService.getDevices(param),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
|
final class RemoteEnergyManagementAnalyticsDevicesService
|
||||||
|
implements AnalyticsDevicesService {
|
||||||
|
const RemoteEnergyManagementAnalyticsDevicesService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<AnalyticsDevice>> getDevices(GetAnalyticsDevicesParam param) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: '/devices-space-community/recursive-child',
|
||||||
|
queryParameters: param.toJson()
|
||||||
|
..addAll({'productType': param.deviceTypes.first}),
|
||||||
|
expectedResponseModel: (response) {
|
||||||
|
final json = response as Map<String, dynamic>;
|
||||||
|
final dailyData = json['data'] as List<dynamic>? ?? <dynamic>[];
|
||||||
|
|
||||||
|
final result = dailyData.map(
|
||||||
|
(json) => AnalyticsDevice.fromJson(json as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
return result.toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load total energy consumption: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
|
class RemoteOccupancyAnalyticsDevicesService implements AnalyticsDevicesService {
|
||||||
|
const RemoteOccupancyAnalyticsDevicesService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<AnalyticsDevice>> getDevices(GetAnalyticsDevicesParam param) async {
|
||||||
|
try {
|
||||||
|
final requests = await Future.wait<List<AnalyticsDevice>>(
|
||||||
|
param.deviceTypes.map((e) {
|
||||||
|
final mappedParam = GetAnalyticsDevicesParam(
|
||||||
|
requestType: AnalyticsDeviceRequestType.occupancy,
|
||||||
|
spaceUuid: param.spaceUuid,
|
||||||
|
deviceTypes: [e],
|
||||||
|
communityUuid: param.communityUuid,
|
||||||
|
);
|
||||||
|
return _makeRequest(mappedParam);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = requests.map((e) => e.first).toList();
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load total energy consumption: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<AnalyticsDevice>> _makeRequest(GetAnalyticsDevicesParam param) async {
|
||||||
|
try {
|
||||||
|
final projectUuid = await ProjectManager.getProjectUUID();
|
||||||
|
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path:
|
||||||
|
'/projects/$projectUuid/communities/${param.communityUuid}/spaces/${param.spaceUuid}/devices',
|
||||||
|
queryParameters: {
|
||||||
|
'communityUuid': param.communityUuid,
|
||||||
|
'spaceUuid': param.spaceUuid,
|
||||||
|
'productType': param.deviceTypes.first,
|
||||||
|
},
|
||||||
|
expectedResponseModel: (response) {
|
||||||
|
final json = response as Map<String, dynamic>;
|
||||||
|
final dailyData = json['data'] as List<dynamic>? ?? <dynamic>[];
|
||||||
|
|
||||||
|
final result = dailyData.map(
|
||||||
|
(json) => AnalyticsDevice.fromJson(json as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
return result.toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,29 +0,0 @@
|
|||||||
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/energy_consumption_by_phases_service.dart';
|
|
||||||
|
|
||||||
class FakeEnergyConsumptionByPhasesService
|
|
||||||
implements EnergyConsumptionByPhasesService {
|
|
||||||
@override
|
|
||||||
Future<List<PhasesEnergyConsumption>> load(
|
|
||||||
GetEnergyConsumptionByPhasesParam param,
|
|
||||||
) {
|
|
||||||
return Future.delayed(
|
|
||||||
const Duration(milliseconds: 500),
|
|
||||||
() => const [
|
|
||||||
PhasesEnergyConsumption(month: 1, phaseA: 200, phaseB: 300, phaseC: 400),
|
|
||||||
PhasesEnergyConsumption(month: 2, phaseA: 300, phaseB: 400, phaseC: 500),
|
|
||||||
PhasesEnergyConsumption(month: 3, phaseA: 400, phaseB: 500, phaseC: 600),
|
|
||||||
PhasesEnergyConsumption(month: 4, phaseA: 100, phaseB: 100, phaseC: 100),
|
|
||||||
PhasesEnergyConsumption(month: 5, phaseA: 300, phaseB: 400, phaseC: 500),
|
|
||||||
PhasesEnergyConsumption(month: 6, phaseA: 300, phaseB: 100, phaseC: 400),
|
|
||||||
PhasesEnergyConsumption(month: 7, phaseA: 300, phaseB: 100, phaseC: 400),
|
|
||||||
PhasesEnergyConsumption(month: 8, phaseA: 500, phaseB: 100, phaseC: 100),
|
|
||||||
PhasesEnergyConsumption(month: 9, phaseA: 500, phaseB: 100, phaseC: 200),
|
|
||||||
PhasesEnergyConsumption(month: 10, phaseA: 100, phaseB: 50, phaseC: 50),
|
|
||||||
PhasesEnergyConsumption(month: 11, phaseA: 600, phaseB: 750, phaseC: 130),
|
|
||||||
PhasesEnergyConsumption(month: 12, phaseA: 100, phaseB: 100, phaseC: 100),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -15,8 +15,9 @@ final class RemoteEnergyConsumptionByPhasesService
|
|||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
final response = await _httpService.get(
|
final response = await _httpService.get(
|
||||||
path: 'endpoint',
|
path: '/power-clamp/${param.powerClampUuid}/historical',
|
||||||
showServerMessage: true,
|
showServerMessage: true,
|
||||||
|
queryParameters: param.toJson(),
|
||||||
expectedResponseModel: (data) {
|
expectedResponseModel: (data) {
|
||||||
final json = data as Map<String, dynamic>? ?? {};
|
final json = data as Map<String, dynamic>? ?? {};
|
||||||
final mappedData = json['data'] as List<dynamic>? ?? [];
|
final mappedData = json['data'] as List<dynamic>? ?? [];
|
||||||
@ -28,7 +29,7 @@ final class RemoteEnergyConsumptionByPhasesService
|
|||||||
);
|
);
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Exception('Failed to load energy consumption per device: $e');
|
throw Exception('Failed to load energy consumption per phase: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,39 +0,0 @@
|
|||||||
import 'dart:math' as math show Random;
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart';
|
|
||||||
|
|
||||||
class FakeEnergyConsumptionPerDeviceService
|
|
||||||
implements EnergyConsumptionPerDeviceService {
|
|
||||||
@override
|
|
||||||
Future<List<DeviceEnergyDataModel>> load(
|
|
||||||
GetEnergyConsumptionPerDeviceParam param,
|
|
||||||
) {
|
|
||||||
final random = math.Random();
|
|
||||||
return Future.delayed(const Duration(milliseconds: 500), () {
|
|
||||||
return [
|
|
||||||
(Colors.redAccent, 1),
|
|
||||||
(Colors.lightBlueAccent, 2),
|
|
||||||
(Colors.purpleAccent, 3),
|
|
||||||
].map((e) {
|
|
||||||
final (color, index) = e;
|
|
||||||
return DeviceEnergyDataModel(
|
|
||||||
color: color,
|
|
||||||
energy: List.generate(30, (i) => i)
|
|
||||||
.map(
|
|
||||||
(index) => EnergyDataModel(
|
|
||||||
date: DateTime(2025, 1, index + 1),
|
|
||||||
value: random.nextInt(100) + (index * 100),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
deviceName: 'Device $index',
|
|
||||||
deviceId: 'device_$index',
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +1,6 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart';
|
||||||
import 'package:syncrow_web/services/api/http_service.dart';
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
@ -15,16 +17,10 @@ class RemoteEnergyConsumptionPerDeviceService
|
|||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
final response = await _httpService.get(
|
final response = await _httpService.get(
|
||||||
path: 'endpoint',
|
path: '/power-clamp/historical',
|
||||||
showServerMessage: true,
|
showServerMessage: true,
|
||||||
expectedResponseModel: (data) {
|
queryParameters: param.toJson(),
|
||||||
final json = data as Map<String, dynamic>? ?? {};
|
expectedResponseModel: _EnergyConsumptionPerDeviceMapper.map,
|
||||||
final mappedData = json['data'] as List<dynamic>? ?? [];
|
|
||||||
return mappedData.map((e) {
|
|
||||||
final jsonData = e as Map<String, dynamic>;
|
|
||||||
return DeviceEnergyDataModel.fromJson(jsonData);
|
|
||||||
}).toList();
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -32,3 +28,30 @@ class RemoteEnergyConsumptionPerDeviceService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract final class _EnergyConsumptionPerDeviceMapper {
|
||||||
|
const _EnergyConsumptionPerDeviceMapper._();
|
||||||
|
static List<DeviceEnergyDataModel> map(dynamic data) {
|
||||||
|
final json = data as Map<String, dynamic>? ?? {};
|
||||||
|
final mappedData = json['data'] as List<dynamic>? ?? [];
|
||||||
|
return mappedData.map((e) {
|
||||||
|
final deviceData = e as Map<String, dynamic>;
|
||||||
|
final energyData = deviceData['data'] as List<dynamic>;
|
||||||
|
|
||||||
|
return DeviceEnergyDataModel(
|
||||||
|
deviceId: deviceData['deviceUuid'] as String,
|
||||||
|
deviceName: deviceData['deviceName'] as String,
|
||||||
|
color: Color((DateTime.now().microsecondsSinceEpoch +
|
||||||
|
deviceData['deviceUuid'].hashCode) |
|
||||||
|
0xFF000000),
|
||||||
|
energy: energyData.map((data) {
|
||||||
|
final energyJson = data as Map<String, dynamic>;
|
||||||
|
return EnergyDataModel(
|
||||||
|
date: DateTime.parse(energyJson['date'] as String),
|
||||||
|
value: double.parse(energyJson['total_energy_consumed_kw'] as String),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
import 'package:syncrow_web/pages/analytics/models/occupacy.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/services/occupacy/occupacy_service.dart';
|
|
||||||
|
|
||||||
class FakeOccupacyService implements OccupacyService {
|
|
||||||
@override
|
|
||||||
Future<List<Occupacy>> load(GetOccupancyParam param) async {
|
|
||||||
return await Future.delayed(
|
|
||||||
const Duration(seconds: 1),
|
|
||||||
() => List.generate(
|
|
||||||
30,
|
|
||||||
(index) => Occupacy(
|
|
||||||
date: DateTime.now().subtract(Duration(days: index)).toString(),
|
|
||||||
occupancy: ((index / 100)).toString(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/occupacy.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/occupacy/occupacy_service.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
|
final class RemoteOccupancyService implements OccupacyService {
|
||||||
|
const RemoteOccupancyService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Occupacy>> load(GetOccupancyParam param) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: '/occupancy/duration/space/${param.spaceUuid}',
|
||||||
|
showServerMessage: true,
|
||||||
|
queryParameters: param.toJson(),
|
||||||
|
expectedResponseModel: (data) {
|
||||||
|
final json = data as Map<String, dynamic>? ?? {};
|
||||||
|
final mappedData = json['data'] as List<dynamic>? ?? [];
|
||||||
|
return mappedData.map((e) {
|
||||||
|
final jsonData = e as Map<String, dynamic>;
|
||||||
|
return Occupacy.fromJson(jsonData);
|
||||||
|
}).toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load energy consumption per phase: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,25 +0,0 @@
|
|||||||
import 'package:syncrow_web/pages/analytics/models/occupancy_heat_map_model.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/occupancy_heat_map_service.dart';
|
|
||||||
|
|
||||||
class FakeOccupancyHeatMapService implements OccupancyHeatMapService {
|
|
||||||
@override
|
|
||||||
Future<List<OccupancyHeatMapModel>> load(GetOccupancyHeatMapParam param) {
|
|
||||||
return Future.delayed(const Duration(milliseconds: 200), () {
|
|
||||||
final now = DateTime.now();
|
|
||||||
final startOfYear = DateTime(now.year, 1, 1);
|
|
||||||
final endOfYear = DateTime(now.year, 12, 31);
|
|
||||||
final daysInYear = endOfYear.difference(startOfYear).inDays + 1;
|
|
||||||
|
|
||||||
final List<OccupancyHeatMapModel> data = List.generate(
|
|
||||||
daysInYear,
|
|
||||||
(index) => OccupancyHeatMapModel(
|
|
||||||
date: startOfYear.add(Duration(days: index)),
|
|
||||||
occupancy: ((index + 1) * 10) % 100,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/occupancy_heat_map_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/occupancy_heat_map_service.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
|
final class RemoteOccupancyHeatMapService implements OccupancyHeatMapService {
|
||||||
|
const RemoteOccupancyHeatMapService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<OccupancyHeatMapModel>> load(GetOccupancyHeatMapParam param) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: '/occupancy/heat-map/space/${param.spaceUuid}',
|
||||||
|
showServerMessage: true,
|
||||||
|
queryParameters: param.toJson(),
|
||||||
|
expectedResponseModel: (response) {
|
||||||
|
final json = response as Map<String, dynamic>;
|
||||||
|
final dailyData = json['data'] as List<dynamic>? ?? <dynamic>[];
|
||||||
|
|
||||||
|
final result = dailyData.map(
|
||||||
|
(json) => OccupancyHeatMapModel.fromJson(json as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
return result.toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load total energy consumption:');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,7 +12,7 @@ class AnalyticsErrorWidget extends StatelessWidget {
|
|||||||
return Visibility(
|
return Visibility(
|
||||||
visible: errorMessage != null || (errorMessage?.isNotEmpty ?? false),
|
visible: errorMessage != null || (errorMessage?.isNotEmpty ?? false),
|
||||||
child: Text(
|
child: Text(
|
||||||
'$errorMessage ?? "Something went wrong"',
|
errorMessage ?? 'Something went wrong',
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
|||||||
@ -63,7 +63,8 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _compareListOfLists(List<List<dynamic>> oldList, List<List<dynamic>> newList) {
|
bool _compareListOfLists(
|
||||||
|
List<List<dynamic>> oldList, List<List<dynamic>> newList) {
|
||||||
// Check if the old and new lists are the same
|
// Check if the old and new lists are the same
|
||||||
if (oldList.length != newList.length) return false;
|
if (oldList.length != newList.length) return false;
|
||||||
|
|
||||||
@ -111,8 +112,8 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
trackVisibility: true,
|
trackVisibility: true,
|
||||||
child: Scrollbar(
|
child: Scrollbar(
|
||||||
controller: _horizontalScrollController,
|
controller: _horizontalScrollController,
|
||||||
thumbVisibility: false,
|
thumbVisibility: true,
|
||||||
trackVisibility: false,
|
trackVisibility: true,
|
||||||
notificationPredicate: (notif) => notif.depth == 1,
|
notificationPredicate: (notif) => notif.depth == 1,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
controller: _verticalScrollController,
|
controller: _verticalScrollController,
|
||||||
@ -132,46 +133,60 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
children: [
|
children: [
|
||||||
if (widget.withCheckBox) _buildSelectAllCheckbox(),
|
if (widget.withCheckBox) _buildSelectAllCheckbox(),
|
||||||
...List.generate(widget.headers.length, (index) {
|
...List.generate(widget.headers.length, (index) {
|
||||||
return _buildTableHeaderCell(widget.headers[index], index);
|
return _buildTableHeaderCell(
|
||||||
|
widget.headers[index], index);
|
||||||
})
|
})
|
||||||
//...widget.headers.map((header) => _buildTableHeaderCell(header)),
|
//...widget.headers.map((header) => _buildTableHeaderCell(header)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
widget.isEmpty
|
widget.isEmpty
|
||||||
? Column(
|
? SizedBox(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
height: widget.size.height * 0.5,
|
||||||
children: [
|
width: widget.size.width,
|
||||||
Row(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
children: [
|
||||||
children: [
|
Row(
|
||||||
Column(
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
SvgPicture.asset(Assets.emptyTable),
|
children: [
|
||||||
const SizedBox(
|
Column(
|
||||||
height: 15,
|
children: [
|
||||||
),
|
SvgPicture.asset(Assets.emptyTable),
|
||||||
Text(
|
const SizedBox(
|
||||||
widget.tableName == 'AccessManagement' ? 'No Password ' : 'No Devices',
|
height: 15,
|
||||||
style: Theme.of(context)
|
),
|
||||||
.textTheme
|
Text(
|
||||||
.bodySmall!
|
widget.tableName == 'AccessManagement'
|
||||||
.copyWith(color: ColorsManager.grayColor),
|
? 'No Password '
|
||||||
)
|
: 'No Devices',
|
||||||
],
|
style: Theme.of(context)
|
||||||
),
|
.textTheme
|
||||||
],
|
.bodySmall!
|
||||||
),
|
.copyWith(
|
||||||
],
|
color:
|
||||||
|
ColorsManager.grayColor),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
: Column(
|
: Column(
|
||||||
children: List.generate(widget.data.length, (index) {
|
children:
|
||||||
|
List.generate(widget.data.length, (index) {
|
||||||
final row = widget.data[index];
|
final row = widget.data[index];
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
if (widget.withCheckBox) _buildRowCheckbox(index, widget.size.height * 0.08),
|
if (widget.withCheckBox)
|
||||||
...row.map((cell) => _buildTableCell(cell.toString(), widget.size.height * 0.08)),
|
_buildRowCheckbox(
|
||||||
|
index, widget.size.height * 0.08),
|
||||||
|
...row.map((cell) => _buildTableCell(
|
||||||
|
cell.toString(),
|
||||||
|
widget.size.height * 0.08)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -196,7 +211,9 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
),
|
),
|
||||||
child: Checkbox(
|
child: Checkbox(
|
||||||
value: _selectAll,
|
value: _selectAll,
|
||||||
onChanged: widget.withSelectAll && widget.data.isNotEmpty ? _toggleSelectAll : null,
|
onChanged: widget.withSelectAll && widget.data.isNotEmpty
|
||||||
|
? _toggleSelectAll
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -238,7 +255,9 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
constraints: const BoxConstraints.expand(height: 40),
|
constraints: const BoxConstraints.expand(height: 40),
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: index == widget.headers.length - 1 ? 12 : 8.0, vertical: 4),
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: index == widget.headers.length - 1 ? 12 : 8.0,
|
||||||
|
vertical: 4),
|
||||||
child: Text(
|
child: Text(
|
||||||
title,
|
title,
|
||||||
style: context.textTheme.titleSmall!.copyWith(
|
style: context.textTheme.titleSmall!.copyWith(
|
||||||
|
|||||||
@ -97,7 +97,8 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
|
|||||||
children: [
|
children: [
|
||||||
_buildInfoRow('Space Name:',
|
_buildInfoRow('Space Name:',
|
||||||
device.spaces?.firstOrNull?.spaceName ?? 'N/A'),
|
device.spaces?.firstOrNull?.spaceName ?? 'N/A'),
|
||||||
_buildInfoRow('Room:', device.subspace?.subspaceName ?? 'N/A'),
|
_buildInfoRow(
|
||||||
|
'Sub space:', device.subspace?.subspaceName ?? 'N/A'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
TableRow(
|
TableRow(
|
||||||
|
|||||||
@ -51,12 +51,13 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
|||||||
|
|
||||||
Future _fetchUserInfo(FetchUserInfo event, Emitter<HomeState> emit) async {
|
Future _fetchUserInfo(FetchUserInfo event, Emitter<HomeState> emit) async {
|
||||||
try {
|
try {
|
||||||
var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
|
var uuid =
|
||||||
|
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
|
||||||
user = await HomeApi().fetchUserInfo(uuid);
|
user = await HomeApi().fetchUserInfo(uuid);
|
||||||
|
|
||||||
if (user != null && user!.project != null) {
|
if (user != null && user!.project != null) {
|
||||||
await ProjectManager.setProjectUUID(user!.project!.uuid);
|
await ProjectManager.setProjectUUID(user!.project!.uuid);
|
||||||
NavigationService.navigatorKey.currentContext!.read<SpaceTreeBloc>().add(InitialEvent());
|
// NavigationService.navigatorKey.currentContext!.read<SpaceTreeBloc>().add(InitialEvent());
|
||||||
}
|
}
|
||||||
add(FetchTermEvent());
|
add(FetchTermEvent());
|
||||||
add(FetchPolicyEvent());
|
add(FetchPolicyEvent());
|
||||||
@ -88,10 +89,12 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _confirmUserAgreement(ConfirmUserAgreementEvent event, Emitter<HomeState> emit) async {
|
Future _confirmUserAgreement(
|
||||||
|
ConfirmUserAgreementEvent event, Emitter<HomeState> emit) async {
|
||||||
try {
|
try {
|
||||||
emit(LoadingHome());
|
emit(LoadingHome());
|
||||||
var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
|
var uuid =
|
||||||
|
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
|
||||||
policy = await HomeApi().confirmUserAgreements(uuid);
|
policy = await HomeApi().confirmUserAgreements(uuid);
|
||||||
emit(PolicyAgreement());
|
emit(PolicyAgreement());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@ -24,7 +24,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final homeBloc = BlocProvider.of<HomeBloc>(context);
|
final homeBloc = BlocProvider.of<HomeBloc>(context);
|
||||||
homeBloc.add(const FetchUserInfo());
|
// homeBloc.add(const FetchUserInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -38,8 +38,10 @@ class _HomeWebPageState extends State<HomeWebPage> {
|
|||||||
child: BlocConsumer<HomeBloc, HomeState>(
|
child: BlocConsumer<HomeBloc, HomeState>(
|
||||||
listener: (BuildContext context, state) {
|
listener: (BuildContext context, state) {
|
||||||
if (state is HomeInitial) {
|
if (state is HomeInitial) {
|
||||||
if (homeBloc.user!.hasAcceptedWebAgreement == false && !_dialogShown) {
|
if (homeBloc.user!.hasAcceptedWebAgreement == false &&
|
||||||
_dialogShown = true; // Set the flag to true to indicate the dialog is showing.
|
!_dialogShown) {
|
||||||
|
_dialogShown =
|
||||||
|
true; // Set the flag to true to indicate the dialog is showing.
|
||||||
Future.delayed(const Duration(seconds: 1), () {
|
Future.delayed(const Duration(seconds: 1), () {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@ -54,7 +56,7 @@ class _HomeWebPageState extends State<HomeWebPage> {
|
|||||||
_dialogShown = false;
|
_dialogShown = false;
|
||||||
if (v != null) {
|
if (v != null) {
|
||||||
homeBloc.add(ConfirmUserAgreementEvent());
|
homeBloc.add(ConfirmUserAgreementEvent());
|
||||||
homeBloc.add(const FetchUserInfo());
|
// homeBloc.add(const FetchUserInfo());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -98,7 +100,8 @@ class _HomeWebPageState extends State<HomeWebPage> {
|
|||||||
width: size.width * 0.68,
|
width: size.width * 0.68,
|
||||||
child: GridView.builder(
|
child: GridView.builder(
|
||||||
itemCount: homeBloc.homeItems.length,
|
itemCount: homeBloc.homeItems.length,
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate:
|
||||||
|
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 3, // Adjust as needed.
|
crossAxisCount: 3, // Adjust as needed.
|
||||||
crossAxisSpacing: 20.0,
|
crossAxisSpacing: 20.0,
|
||||||
mainAxisSpacing: 20.0,
|
mainAxisSpacing: 20.0,
|
||||||
@ -110,7 +113,8 @@ class _HomeWebPageState extends State<HomeWebPage> {
|
|||||||
active: homeBloc.homeItems[index].active!,
|
active: homeBloc.homeItems[index].active!,
|
||||||
name: homeBloc.homeItems[index].title!,
|
name: homeBloc.homeItems[index].title!,
|
||||||
img: homeBloc.homeItems[index].icon!,
|
img: homeBloc.homeItems[index].icon!,
|
||||||
onTap: () => homeBloc.homeItems[index].onPress(context),
|
onTap: () =>
|
||||||
|
homeBloc.homeItems[index].onPress(context),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@ -21,7 +21,8 @@ import 'package:syncrow_web/services/space_mana_api.dart';
|
|||||||
import 'package:syncrow_web/services/space_model_mang_api.dart';
|
import 'package:syncrow_web/services/space_model_mang_api.dart';
|
||||||
import 'package:syncrow_web/utils/constants/action_enum.dart' as custom_action;
|
import 'package:syncrow_web/utils/constants/action_enum.dart' as custom_action;
|
||||||
|
|
||||||
class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementState> {
|
class SpaceManagementBloc
|
||||||
|
extends Bloc<SpaceManagementEvent, SpaceManagementState> {
|
||||||
final CommunitySpaceManagementApi _api;
|
final CommunitySpaceManagementApi _api;
|
||||||
final ProductApi _productApi;
|
final ProductApi _productApi;
|
||||||
final SpaceModelManagementApi _spaceModelApi;
|
final SpaceModelManagementApi _spaceModelApi;
|
||||||
@ -62,7 +63,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
int page = 1;
|
int page = 1;
|
||||||
|
|
||||||
while (hasNext) {
|
while (hasNext) {
|
||||||
final spaceModels = await _spaceModelApi.listSpaceModels(page: page, projectId: projectUuid);
|
final spaceModels = await _spaceModelApi.listSpaceModels(
|
||||||
|
page: page, projectId: projectUuid);
|
||||||
if (spaceModels.isNotEmpty) {
|
if (spaceModels.isNotEmpty) {
|
||||||
allSpaceModels.addAll(spaceModels);
|
allSpaceModels.addAll(spaceModels);
|
||||||
page++;
|
page++;
|
||||||
@ -75,26 +77,29 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
await fetchTags();
|
await fetchTags();
|
||||||
|
|
||||||
emit(SpaceModelLoaded(
|
emit(SpaceModelLoaded(
|
||||||
communities:
|
communities: state is SpaceManagementLoaded
|
||||||
state is SpaceManagementLoaded ? (state as SpaceManagementLoaded).communities : [],
|
? (state as SpaceManagementLoaded).communities
|
||||||
|
: [],
|
||||||
products: _cachedProducts ?? [],
|
products: _cachedProducts ?? [],
|
||||||
spaceModels: List.from(_cachedSpaceModels ?? []),
|
spaceModels: List.from(_cachedSpaceModels ?? []),
|
||||||
allTags: _cachedTags ?? []));
|
allTags: _cachedTags ?? []));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _deleteSpaceModelFromCache(
|
void _deleteSpaceModelFromCache(DeleteSpaceModelFromCache event,
|
||||||
DeleteSpaceModelFromCache event, Emitter<SpaceManagementState> emit) async {
|
Emitter<SpaceManagementState> emit) async {
|
||||||
if (_cachedSpaceModels != null) {
|
if (_cachedSpaceModels != null) {
|
||||||
_cachedSpaceModels =
|
_cachedSpaceModels = _cachedSpaceModels!
|
||||||
_cachedSpaceModels!.where((model) => model.uuid != event.deletedUuid).toList();
|
.where((model) => model.uuid != event.deletedUuid)
|
||||||
|
.toList();
|
||||||
} else {
|
} else {
|
||||||
_cachedSpaceModels = await fetchSpaceModels();
|
_cachedSpaceModels = await fetchSpaceModels();
|
||||||
}
|
}
|
||||||
await fetchTags();
|
await fetchTags();
|
||||||
|
|
||||||
emit(SpaceModelLoaded(
|
emit(SpaceModelLoaded(
|
||||||
communities:
|
communities: state is SpaceManagementLoaded
|
||||||
state is SpaceManagementLoaded ? (state as SpaceManagementLoaded).communities : [],
|
? (state as SpaceManagementLoaded).communities
|
||||||
|
: [],
|
||||||
products: _cachedProducts ?? [],
|
products: _cachedProducts ?? [],
|
||||||
spaceModels: List.from(_cachedSpaceModels ?? []),
|
spaceModels: List.from(_cachedSpaceModels ?? []),
|
||||||
allTags: _cachedTags ?? []));
|
allTags: _cachedTags ?? []));
|
||||||
@ -122,8 +127,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
int page = 1;
|
int page = 1;
|
||||||
|
|
||||||
while (hasNext) {
|
while (hasNext) {
|
||||||
final spaceModels =
|
final spaceModels = await _spaceModelApi.listSpaceModels(
|
||||||
await _spaceModelApi.listSpaceModels(page: page, projectId: projectUuid);
|
page: page, projectId: projectUuid);
|
||||||
if (spaceModels.isNotEmpty) {
|
if (spaceModels.isNotEmpty) {
|
||||||
allSpaceModels.addAll(spaceModels);
|
allSpaceModels.addAll(spaceModels);
|
||||||
page++;
|
page++;
|
||||||
@ -164,10 +169,12 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
await fetchTags();
|
await fetchTags();
|
||||||
|
|
||||||
emit(SpaceManagementLoading());
|
emit(SpaceManagementLoading());
|
||||||
final success = await _api.updateCommunity(event.communityUuid, event.name, projectUuid);
|
final success = await _api.updateCommunity(
|
||||||
|
event.communityUuid, event.name, projectUuid);
|
||||||
if (success) {
|
if (success) {
|
||||||
if (previousState is SpaceManagementLoaded) {
|
if (previousState is SpaceManagementLoaded) {
|
||||||
final updatedCommunities = List<CommunityModel>.from(previousState.communities);
|
final updatedCommunities =
|
||||||
|
List<CommunityModel>.from(previousState.communities);
|
||||||
for (var community in updatedCommunities) {
|
for (var community in updatedCommunities) {
|
||||||
if (community.uuid == event.communityUuid) {
|
if (community.uuid == event.communityUuid) {
|
||||||
community.name = event.name;
|
community.name = event.name;
|
||||||
@ -212,7 +219,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<SpaceModel>> _fetchSpacesForCommunity(String communityUuid) async {
|
Future<List<SpaceModel>> _fetchSpacesForCommunity(
|
||||||
|
String communityUuid) async {
|
||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
|
|
||||||
return await _api.getSpaceHierarchy(communityUuid, projectUuid);
|
return await _api.getSpaceHierarchy(communityUuid, projectUuid);
|
||||||
@ -242,20 +250,23 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onBlankState(BlankStateEvent event, Emitter<SpaceManagementState> emit) async {
|
Future<void> _onBlankState(
|
||||||
|
BlankStateEvent event, Emitter<SpaceManagementState> emit) async {
|
||||||
try {
|
try {
|
||||||
final previousState = state;
|
final previousState = state;
|
||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
var spaceBloc = event.context.read<SpaceTreeBloc>();
|
var spaceBloc = event.context.read<SpaceTreeBloc>();
|
||||||
var spaceTreeState = event.context.read<SpaceTreeBloc>().state;
|
var spaceTreeState = event.context.read<SpaceTreeBloc>().state;
|
||||||
|
|
||||||
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc, spaceTreeState);
|
List<CommunityModel> communities =
|
||||||
|
await _waitForCommunityList(spaceBloc, spaceTreeState);
|
||||||
await fetchSpaceModels();
|
await fetchSpaceModels();
|
||||||
await fetchTags();
|
// await fetchTags();
|
||||||
|
|
||||||
var prevSpaceModels = await fetchSpaceModels();
|
var prevSpaceModels = await fetchSpaceModels();
|
||||||
|
|
||||||
if (previousState is SpaceManagementLoaded || previousState is BlankState) {
|
if (previousState is SpaceManagementLoaded ||
|
||||||
|
previousState is BlankState) {
|
||||||
final prevCommunities = (previousState as dynamic).communities ?? [];
|
final prevCommunities = (previousState as dynamic).communities ?? [];
|
||||||
emit(BlankState(
|
emit(BlankState(
|
||||||
communities: List<CommunityModel>.from(prevCommunities),
|
communities: List<CommunityModel>.from(prevCommunities),
|
||||||
@ -286,7 +297,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
_onloadProducts();
|
_onloadProducts();
|
||||||
await fetchTags();
|
await fetchTags();
|
||||||
// Wait until `communityList` is loaded
|
// Wait until `communityList` is loaded
|
||||||
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc, spaceTreeState);
|
List<CommunityModel> communities =
|
||||||
|
await _waitForCommunityList(spaceBloc, spaceTreeState);
|
||||||
|
|
||||||
// Fetch space models after communities are available
|
// Fetch space models after communities are available
|
||||||
final prevSpaceModels = await fetchSpaceModels();
|
final prevSpaceModels = await fetchSpaceModels();
|
||||||
@ -310,8 +322,9 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
final completer = Completer<List<CommunityModel>>();
|
final completer = Completer<List<CommunityModel>>();
|
||||||
final subscription = spaceBloc.stream.listen((state) {
|
final subscription = spaceBloc.stream.listen((state) {
|
||||||
if (!completer.isCompleted && state.communityList.isNotEmpty) {
|
if (!completer.isCompleted && state.communityList.isNotEmpty) {
|
||||||
completer
|
completer.complete(state.searchQuery.isNotEmpty
|
||||||
.complete(state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList);
|
? state.filteredCommunity
|
||||||
|
: state.communityList);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
@ -339,7 +352,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
emit(SpaceManagementLoading());
|
emit(SpaceManagementLoading());
|
||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
|
|
||||||
final success = await _api.deleteCommunity(event.communityUuid, projectUuid);
|
final success =
|
||||||
|
await _api.deleteCommunity(event.communityUuid, projectUuid);
|
||||||
if (success) {
|
if (success) {
|
||||||
// add(LoadCommunityAndSpacesEvent());
|
// add(LoadCommunityAndSpacesEvent());
|
||||||
} else {
|
} else {
|
||||||
@ -361,12 +375,13 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
try {
|
try {
|
||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
await fetchTags();
|
await fetchTags();
|
||||||
CommunityModel? newCommunity =
|
CommunityModel? newCommunity = await _api.createCommunity(
|
||||||
await _api.createCommunity(event.name, event.description, projectUuid);
|
event.name, event.description, projectUuid);
|
||||||
var prevSpaceModels = await fetchSpaceModels();
|
var prevSpaceModels = await fetchSpaceModels();
|
||||||
|
|
||||||
if (newCommunity != null) {
|
if (newCommunity != null) {
|
||||||
if (previousState is SpaceManagementLoaded || previousState is BlankState) {
|
if (previousState is SpaceManagementLoaded ||
|
||||||
|
previousState is BlankState) {
|
||||||
final prevCommunities = List<CommunityModel>.from(
|
final prevCommunities = List<CommunityModel>.from(
|
||||||
(previousState as dynamic).communities,
|
(previousState as dynamic).communities,
|
||||||
);
|
);
|
||||||
@ -459,12 +474,15 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
try {
|
try {
|
||||||
final spaceTreeState = event.context.read<SpaceTreeBloc>().state;
|
final spaceTreeState = event.context.read<SpaceTreeBloc>().state;
|
||||||
|
|
||||||
final updatedSpaces =
|
final updatedSpaces = await saveSpacesHierarchically(
|
||||||
await saveSpacesHierarchically(event.context, event.spaces, event.communityUuid);
|
event.context, event.spaces, event.communityUuid);
|
||||||
final allSpaces = await _fetchSpacesForCommunity(event.communityUuid);
|
final allSpaces = await _fetchSpacesForCommunity(event.communityUuid);
|
||||||
|
|
||||||
emit(SpaceCreationSuccess(spaces: updatedSpaces));
|
// emit(SpaceCreationSuccess(spaces: updatedSpaces));
|
||||||
|
// updatedSpaces.forEach(
|
||||||
|
// (element) => element.uuid,
|
||||||
|
// );
|
||||||
|
// final lastUpdatedSpaced = updatedSpaces..addAll(allSpaces);
|
||||||
if (previousState is SpaceManagementLoaded) {
|
if (previousState is SpaceManagementLoaded) {
|
||||||
await _updateLoadedState(
|
await _updateLoadedState(
|
||||||
spaceTreeState,
|
spaceTreeState,
|
||||||
@ -475,7 +493,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(SpaceManagementError('Error saving spaces: $e'));
|
// emit(SpaceManagementError('Error saving spaces: $e'));
|
||||||
|
|
||||||
if (previousState is SpaceManagementLoaded) {
|
if (previousState is SpaceManagementLoaded) {
|
||||||
emit(previousState);
|
emit(previousState);
|
||||||
@ -515,13 +533,15 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
emit(previousState);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
rethrow;
|
emit(previousState);
|
||||||
|
// rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<SpaceModel>> saveSpacesHierarchically(
|
Future<List<SpaceModel>> saveSpacesHierarchically(BuildContext context,
|
||||||
BuildContext context, List<SpaceModel> spaces, String communityUuid) async {
|
List<SpaceModel> spaces, String communityUuid) async {
|
||||||
final orderedSpaces = flattenHierarchy(spaces);
|
final orderedSpaces = flattenHierarchy(spaces);
|
||||||
|
|
||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
@ -534,6 +554,14 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
|
|
||||||
selectedCommunity = filteredCommunities.firstWhere(
|
selectedCommunity = filteredCommunities.firstWhere(
|
||||||
(community) => community.uuid == communityUuid,
|
(community) => community.uuid == communityUuid,
|
||||||
|
orElse: () => CommunityModel(
|
||||||
|
uuid: '',
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
spaces: spaces,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return [];
|
return [];
|
||||||
@ -548,9 +576,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
if (parent.uuid != null) {
|
if (parent.uuid != null) {
|
||||||
await _api.deleteSpace(communityUuid, parent.uuid!, projectUuid);
|
await _api.deleteSpace(communityUuid, parent.uuid!, projectUuid);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
orderedSpaces.removeWhere((space) => parentsToDelete.contains(space));
|
orderedSpaces.removeWhere((space) => parentsToDelete.contains(space));
|
||||||
|
|
||||||
@ -564,7 +590,7 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
|
|
||||||
if (matchedSpaces.isEmpty) continue;
|
if (matchedSpaces.isEmpty) continue;
|
||||||
|
|
||||||
final prevSpace = matchedSpaces[0];
|
final prevSpace = matchedSpaces.elementAtOrNull(0);
|
||||||
|
|
||||||
final List<UpdateSubspaceTemplateModel> subspaceUpdates = [];
|
final List<UpdateSubspaceTemplateModel> subspaceUpdates = [];
|
||||||
final List<SubspaceModel>? prevSubspaces = prevSpace?.subspaces;
|
final List<SubspaceModel>? prevSubspaces = prevSpace?.subspaces;
|
||||||
@ -575,17 +601,19 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
if (prevSubspaces != null || newSubspaces != null) {
|
if (prevSubspaces != null || newSubspaces != null) {
|
||||||
if (prevSubspaces != null && newSubspaces != null) {
|
if (prevSubspaces != null && newSubspaces != null) {
|
||||||
for (var prevSubspace in prevSubspaces) {
|
for (var prevSubspace in prevSubspaces) {
|
||||||
final existsInNew =
|
final existsInNew = newSubspaces
|
||||||
newSubspaces.any((subspace) => subspace.uuid == prevSubspace.uuid);
|
.any((subspace) => subspace.uuid == prevSubspace.uuid);
|
||||||
if (!existsInNew) {
|
if (!existsInNew) {
|
||||||
subspaceUpdates.add(UpdateSubspaceTemplateModel(
|
subspaceUpdates.add(UpdateSubspaceTemplateModel(
|
||||||
action: custom_action.Action.delete, uuid: prevSubspace.uuid));
|
action: custom_action.Action.delete,
|
||||||
|
uuid: prevSubspace.uuid));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (prevSubspaces != null && newSubspaces == null) {
|
} else if (prevSubspaces != null && newSubspaces == null) {
|
||||||
for (var prevSubspace in prevSubspaces) {
|
for (var prevSubspace in prevSubspaces) {
|
||||||
subspaceUpdates.add(UpdateSubspaceTemplateModel(
|
subspaceUpdates.add(UpdateSubspaceTemplateModel(
|
||||||
action: custom_action.Action.delete, uuid: prevSubspace.uuid));
|
action: custom_action.Action.delete,
|
||||||
|
uuid: prevSubspace.uuid));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -613,7 +641,9 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (prevSubspaces != null && newSubspaces != null) {
|
if (prevSubspaces != null && newSubspaces != null) {
|
||||||
final newSubspaceMap = {for (var subspace in newSubspaces) subspace.uuid: subspace};
|
final newSubspaceMap = {
|
||||||
|
for (var subspace in newSubspaces) subspace.uuid: subspace
|
||||||
|
};
|
||||||
|
|
||||||
for (var prevSubspace in prevSubspaces) {
|
for (var prevSubspace in prevSubspaces) {
|
||||||
final newSubspace = newSubspaceMap[prevSubspace.uuid];
|
final newSubspace = newSubspaceMap[prevSubspace.uuid];
|
||||||
@ -639,9 +669,10 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
isPrivate: space.isPrivate,
|
isPrivate: space.isPrivate,
|
||||||
position: space.position,
|
position: space.position,
|
||||||
icon: space.icon,
|
icon: space.icon,
|
||||||
subspaces: subspaceUpdates,
|
subspaces: space.subspaces,
|
||||||
tags: tagUpdates,
|
// subspaceUpdates,
|
||||||
direction: space.incomingConnection?.direction,
|
tags: space.tags,
|
||||||
|
// tagUpdates,
|
||||||
spaceModelUuid: space.spaceModel?.uuid,
|
spaceModelUuid: space.spaceModel?.uuid,
|
||||||
projectId: projectUuid);
|
projectId: projectUuid);
|
||||||
} else {
|
} else {
|
||||||
@ -651,8 +682,10 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
: [];
|
: [];
|
||||||
|
|
||||||
var createSubspaceBodyModels = space.subspaces?.map((subspace) {
|
var createSubspaceBodyModels = space.subspaces?.map((subspace) {
|
||||||
final tagBodyModels =
|
final tagBodyModels = subspace.tags
|
||||||
subspace.tags?.map((tag) => tag.toCreateTagBodyModel()).toList() ?? [];
|
?.map((tag) => tag.toCreateTagBodyModel())
|
||||||
|
.toList() ??
|
||||||
|
[];
|
||||||
return CreateSubspaceModel()
|
return CreateSubspaceModel()
|
||||||
..subspaceName = subspace.subspaceName
|
..subspaceName = subspace.subspaceName
|
||||||
..tags = tagBodyModels;
|
..tags = tagBodyModels;
|
||||||
@ -671,7 +704,6 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
isPrivate: space.isPrivate,
|
isPrivate: space.isPrivate,
|
||||||
position: space.position,
|
position: space.position,
|
||||||
icon: space.icon,
|
icon: space.icon,
|
||||||
direction: space.incomingConnection?.direction,
|
|
||||||
spaceModelUuid: space.spaceModel?.uuid,
|
spaceModelUuid: space.spaceModel?.uuid,
|
||||||
tags: tagBodyModels,
|
tags: tagBodyModels,
|
||||||
subspaces: createSubspaceBodyModels,
|
subspaces: createSubspaceBodyModels,
|
||||||
@ -679,7 +711,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
space.uuid = response?.uuid;
|
space.uuid = response?.uuid;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow; // Stop further execution on failure
|
return [];
|
||||||
|
// Stop further execution on failure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return spaces;
|
return spaces;
|
||||||
@ -710,7 +743,8 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
return result.toList(); // Convert back to a list
|
return result.toList(); // Convert back to a list
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onLoadSpaceModel(SpaceModelLoadEvent event, Emitter<SpaceManagementState> emit) async {
|
void _onLoadSpaceModel(
|
||||||
|
SpaceModelLoadEvent event, Emitter<SpaceManagementState> emit) async {
|
||||||
emit(SpaceManagementLoading());
|
emit(SpaceManagementLoading());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -757,14 +791,17 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
// Case 1: Tags deleted
|
// Case 1: Tags deleted
|
||||||
if (prevTags != null && newTags != null) {
|
if (prevTags != null && newTags != null) {
|
||||||
for (var prevTag in prevTags) {
|
for (var prevTag in prevTags) {
|
||||||
final existsInNew = newTags.any((newTag) => newTag.uuid == prevTag.uuid);
|
final existsInNew =
|
||||||
|
newTags.any((newTag) => newTag.uuid == prevTag.uuid);
|
||||||
if (!existsInNew) {
|
if (!existsInNew) {
|
||||||
tagUpdates.add(TagModelUpdate(action: custom_action.Action.delete, uuid: prevTag.uuid));
|
tagUpdates.add(TagModelUpdate(
|
||||||
|
action: custom_action.Action.delete, uuid: prevTag.uuid));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (prevTags != null && newTags == null) {
|
} else if (prevTags != null && newTags == null) {
|
||||||
for (var prevTag in prevTags) {
|
for (var prevTag in prevTags) {
|
||||||
tagUpdates.add(TagModelUpdate(action: custom_action.Action.delete, uuid: prevTag.uuid));
|
tagUpdates.add(TagModelUpdate(
|
||||||
|
action: custom_action.Action.delete, uuid: prevTag.uuid));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -807,15 +844,16 @@ class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementStat
|
|||||||
return tagUpdates;
|
return tagUpdates;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<SpaceModel> findMatchingSpaces(List<SpaceModel> spaces, String targetUuid) {
|
List<SpaceModel> findMatchingSpaces(
|
||||||
|
List<SpaceModel> spaces, String targetUuid) {
|
||||||
List<SpaceModel> matched = [];
|
List<SpaceModel> matched = [];
|
||||||
|
|
||||||
for (var space in spaces) {
|
for (var space in spaces) {
|
||||||
if (space.uuid == targetUuid) {
|
if (space.uuid == targetUuid) {
|
||||||
matched.add(space);
|
matched.add(space);
|
||||||
}
|
}
|
||||||
matched
|
matched.addAll(findMatchingSpaces(
|
||||||
.addAll(findMatchingSpaces(space.children, targetUuid)); // Recursively search in children
|
space.children, targetUuid)); // Recursively search in children
|
||||||
}
|
}
|
||||||
|
|
||||||
return matched;
|
return matched;
|
||||||
|
|||||||
@ -3,23 +3,26 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model
|
|||||||
class Connection {
|
class Connection {
|
||||||
final SpaceModel startSpace;
|
final SpaceModel startSpace;
|
||||||
final SpaceModel endSpace;
|
final SpaceModel endSpace;
|
||||||
final String direction;
|
|
||||||
|
|
||||||
Connection({required this.startSpace, required this.endSpace, required this.direction});
|
Connection({
|
||||||
|
required this.startSpace,
|
||||||
|
required this.endSpace,
|
||||||
|
});
|
||||||
|
|
||||||
Map<String, dynamic> toMap() {
|
Map<String, dynamic> toMap() {
|
||||||
return {
|
return {
|
||||||
'startUuid': startSpace.uuid ?? 'unsaved-start-space-${startSpace.name}', // Fallback for unsaved spaces
|
'startUuid': startSpace.uuid ??
|
||||||
'endUuid': endSpace.uuid ?? 'unsaved-end-space-${endSpace.name}', // Fallback for unsaved spaces
|
'unsaved-start-space-${startSpace.name}', // Fallback for unsaved spaces
|
||||||
'direction': direction,
|
'endUuid': endSpace.uuid ??
|
||||||
|
'unsaved-end-space-${endSpace.name}', // Fallback for unsaved spaces
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static Connection fromMap(Map<String, dynamic> map, Map<String, SpaceModel> spaces) {
|
static Connection fromMap(
|
||||||
|
Map<String, dynamic> map, Map<String, SpaceModel> spaces) {
|
||||||
return Connection(
|
return Connection(
|
||||||
startSpace: spaces[map['startUuid']]!,
|
startSpace: spaces[map['startUuid']]!,
|
||||||
endSpace: spaces[map['endUuid']]!,
|
endSpace: spaces[map['endUuid']]!,
|
||||||
direction: map['direction'],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
|
||||||
|
import 'selected_product_model.dart';
|
||||||
|
|
||||||
class ProductModel {
|
class ProductModel {
|
||||||
final String uuid;
|
final String uuid;
|
||||||
final String catName;
|
final String catName;
|
||||||
@ -38,6 +40,15 @@ class ProductModel {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SelectedProduct toSelectedProduct(int count) {
|
||||||
|
return SelectedProduct(
|
||||||
|
productId: uuid,
|
||||||
|
count: count,
|
||||||
|
productName: name!,
|
||||||
|
product: this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static String _mapIconToProduct(String prodType) {
|
static String _mapIconToProduct(String prodType) {
|
||||||
const iconMapping = {
|
const iconMapping = {
|
||||||
'1G': Assets.Gang1SwitchIcon,
|
'1G': Assets.Gang1SwitchIcon,
|
||||||
|
|||||||
@ -101,7 +101,7 @@ class SpaceModel {
|
|||||||
spaceModel: json['spaceModel'] != null
|
spaceModel: json['spaceModel'] != null
|
||||||
? SpaceTemplateModel.fromJson(json['spaceModel'])
|
? SpaceTemplateModel.fromJson(json['spaceModel'])
|
||||||
: null,
|
: null,
|
||||||
tags: (json['tags'] as List<dynamic>?)
|
tags: (json['productAllocations'] as List<dynamic>?)
|
||||||
?.where((item) => item is Map<String, dynamic>) // Validate type
|
?.where((item) => item is Map<String, dynamic>) // Validate type
|
||||||
.map((item) => Tag.fromJson(item as Map<String, dynamic>))
|
.map((item) => Tag.fromJson(item as Map<String, dynamic>))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
@ -116,7 +116,6 @@ class SpaceModel {
|
|||||||
instance.incomingConnection = Connection(
|
instance.incomingConnection = Connection(
|
||||||
startSpace: instance.parent ?? instance, // Parent space
|
startSpace: instance.parent ?? instance, // Parent space
|
||||||
endSpace: instance, // This space instance
|
endSpace: instance, // This space instance
|
||||||
direction: conn['direction'],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,7 @@ class SubspaceModel {
|
|||||||
subspaceName: json['subspaceName'] ?? '',
|
subspaceName: json['subspaceName'] ?? '',
|
||||||
disabled: json['disabled'] ?? false,
|
disabled: json['disabled'] ?? false,
|
||||||
internalId: internalId,
|
internalId: internalId,
|
||||||
tags: (json['tags'] as List<dynamic>?)
|
tags: (json['productAllocations'] as List<dynamic>?)
|
||||||
?.map((item) => Tag.fromJson(item))
|
?.map((item) => Tag.fromJson(item))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
[],
|
[],
|
||||||
@ -36,7 +36,7 @@ class SubspaceModel {
|
|||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {
|
||||||
'uuid': uuid,
|
if (uuid != null) 'uuid': uuid,
|
||||||
'subspaceName': subspaceName,
|
'subspaceName': subspaceName,
|
||||||
'disabled': disabled,
|
'disabled': disabled,
|
||||||
'tags': tags?.map((e) => e.toJson()).toList() ?? [],
|
'tags': tags?.map((e) => e.toJson()).toList() ?? [],
|
||||||
|
|||||||
@ -23,10 +23,13 @@ class Tag extends BaseTag {
|
|||||||
final String internalId = json['internalId'] ?? const Uuid().v4();
|
final String internalId = json['internalId'] ?? const Uuid().v4();
|
||||||
|
|
||||||
return Tag(
|
return Tag(
|
||||||
uuid: json['uuid'] ?? '',
|
//TODO:insure UUId for tag or prodAlloc
|
||||||
|
uuid: json['name'] != null ? json['uuid'] : json['tag']?['uuid'] ?? '',
|
||||||
internalId: internalId,
|
internalId: internalId,
|
||||||
tag: json['name'] ?? '',
|
tag: json['name'] ?? json['tag']?['name'] ?? '',
|
||||||
product: json['product'] != null ? ProductModel.fromMap(json['product']) : null,
|
product: json['product'] != null
|
||||||
|
? ProductModel.fromMap(json['product'])
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,9 +52,10 @@ class Tag extends BaseTag {
|
|||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {
|
||||||
'uuid': uuid,
|
if (uuid != null) 'uuid': uuid,
|
||||||
'tag': tag,
|
'name': tag,
|
||||||
'product': product?.toMap(),
|
'productUuid': product?.uuid,
|
||||||
|
// .toMap(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,7 +67,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
|
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
|
||||||
connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
|
connections =
|
||||||
|
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
|
||||||
_adjustCanvasSizeForSpaces();
|
_adjustCanvasSizeForSpaces();
|
||||||
_nameController = TextEditingController(
|
_nameController = TextEditingController(
|
||||||
text: widget.selectedCommunity?.name ?? '',
|
text: widget.selectedCommunity?.name ?? '',
|
||||||
@ -96,13 +97,15 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
if (oldWidget.spaces != widget.spaces) {
|
if (oldWidget.spaces != widget.spaces) {
|
||||||
setState(() {
|
setState(() {
|
||||||
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
|
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
|
||||||
connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
|
connections =
|
||||||
|
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
|
||||||
_adjustCanvasSizeForSpaces();
|
_adjustCanvasSizeForSpaces();
|
||||||
realignTree();
|
realignTree();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (widget.selectedSpace != oldWidget.selectedSpace && widget.selectedSpace != null) {
|
if (widget.selectedSpace != oldWidget.selectedSpace &&
|
||||||
|
widget.selectedSpace != null) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_moveToSpace(widget.selectedSpace!);
|
_moveToSpace(widget.selectedSpace!);
|
||||||
});
|
});
|
||||||
@ -185,7 +188,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
connection, widget.selectedSpace)
|
connection, widget.selectedSpace)
|
||||||
? 1.0
|
? 1.0
|
||||||
: 0.3, // Adjust opacity
|
: 0.3, // Adjust opacity
|
||||||
child: CustomPaint(painter: CurvedLinePainter([connection])),
|
child: CustomPaint(
|
||||||
|
painter: CurvedLinePainter([connection])),
|
||||||
),
|
),
|
||||||
for (var entry in spaces.asMap().entries)
|
for (var entry in spaces.asMap().entries)
|
||||||
if (entry.value.status != SpaceStatus.deleted &&
|
if (entry.value.status != SpaceStatus.deleted &&
|
||||||
@ -195,11 +199,11 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
top: entry.value.position.dy,
|
top: entry.value.position.dy,
|
||||||
child: SpaceCardWidget(
|
child: SpaceCardWidget(
|
||||||
index: entry.key,
|
index: entry.key,
|
||||||
onButtonTap: (int index, Offset newPosition, String direction) {
|
onButtonTap: (int index, Offset newPosition) {
|
||||||
_showCreateSpaceDialog(screenSize,
|
_showCreateSpaceDialog(screenSize,
|
||||||
position: spaces[index].position + newPosition,
|
position:
|
||||||
|
spaces[index].position + newPosition,
|
||||||
parentIndex: index,
|
parentIndex: index,
|
||||||
direction: direction,
|
|
||||||
projectTags: widget.projectTags);
|
projectTags: widget.projectTags);
|
||||||
},
|
},
|
||||||
position: entry.value.position,
|
position: entry.value.position,
|
||||||
@ -210,8 +214,9 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
_updateNodePosition(entry.value, newPosition);
|
_updateNodePosition(entry.value, newPosition);
|
||||||
},
|
},
|
||||||
buildSpaceContainer: (int index) {
|
buildSpaceContainer: (int index) {
|
||||||
final bool isHighlighted = SpaceHelper.isHighlightedSpace(
|
final bool isHighlighted =
|
||||||
spaces[index], widget.selectedSpace);
|
SpaceHelper.isHighlightedSpace(
|
||||||
|
spaces[index], widget.selectedSpace);
|
||||||
|
|
||||||
return Opacity(
|
return Opacity(
|
||||||
opacity: isHighlighted ? 1.0 : 0.3,
|
opacity: isHighlighted ? 1.0 : 0.3,
|
||||||
@ -289,7 +294,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
void _showCreateSpaceDialog(Size screenSize,
|
void _showCreateSpaceDialog(Size screenSize,
|
||||||
{Offset? position,
|
{Offset? position,
|
||||||
int? parentIndex,
|
int? parentIndex,
|
||||||
String? direction,
|
|
||||||
double? canvasWidth,
|
double? canvasWidth,
|
||||||
double? canvasHeight,
|
double? canvasHeight,
|
||||||
required List<Tag> projectTags}) {
|
required List<Tag> projectTags}) {
|
||||||
@ -299,19 +303,25 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
return CreateSpaceDialog(
|
return CreateSpaceDialog(
|
||||||
products: widget.products,
|
products: widget.products,
|
||||||
spaceModels: widget.spaceModels,
|
spaceModels: widget.spaceModels,
|
||||||
allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
|
allTags:
|
||||||
|
TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
|
||||||
parentSpace: parentIndex != null ? spaces[parentIndex] : null,
|
parentSpace: parentIndex != null ? spaces[parentIndex] : null,
|
||||||
projectTags: projectTags,
|
projectTags: projectTags,
|
||||||
onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts,
|
onCreateSpace: (String name,
|
||||||
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) {
|
String icon,
|
||||||
|
List<SelectedProduct> selectedProducts,
|
||||||
|
SpaceTemplateModel? spaceModel,
|
||||||
|
List<SubspaceModel>? subspaces,
|
||||||
|
List<Tag>? tags) {
|
||||||
setState(() {
|
setState(() {
|
||||||
// Set the first space in the center or use passed position
|
// Set the first space in the center or use passed position
|
||||||
Offset newPosition;
|
Offset newPosition;
|
||||||
if (parentIndex != null) {
|
if (parentIndex != null) {
|
||||||
newPosition =
|
newPosition = getBalancedChildPosition(
|
||||||
getBalancedChildPosition(spaces[parentIndex]); // Ensure balanced position
|
spaces[parentIndex]); // Ensure balanced position
|
||||||
} else {
|
} else {
|
||||||
newPosition = position ?? ConnectionHelper.getCenterPosition(screenSize);
|
newPosition =
|
||||||
|
position ?? ConnectionHelper.getCenterPosition(screenSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
SpaceModel newSpace = SpaceModel(
|
SpaceModel newSpace = SpaceModel(
|
||||||
@ -325,14 +335,13 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
subspaces: subspaces,
|
subspaces: subspaces,
|
||||||
tags: tags);
|
tags: tags);
|
||||||
|
|
||||||
if (parentIndex != null && direction != null) {
|
if (parentIndex != null) {
|
||||||
SpaceModel parentSpace = spaces[parentIndex];
|
SpaceModel parentSpace = spaces[parentIndex];
|
||||||
parentSpace.internalId = spaces[parentIndex].internalId;
|
parentSpace.internalId = spaces[parentIndex].internalId;
|
||||||
newSpace.parent = parentSpace;
|
newSpace.parent = parentSpace;
|
||||||
final newConnection = Connection(
|
final newConnection = Connection(
|
||||||
startSpace: parentSpace,
|
startSpace: parentSpace,
|
||||||
endSpace: newSpace,
|
endSpace: newSpace,
|
||||||
direction: direction,
|
|
||||||
);
|
);
|
||||||
connections.add(newConnection);
|
connections.add(newConnection);
|
||||||
newSpace.incomingConnection = newConnection;
|
newSpace.incomingConnection = newConnection;
|
||||||
@ -360,16 +369,21 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
name: widget.selectedSpace!.name,
|
name: widget.selectedSpace!.name,
|
||||||
icon: widget.selectedSpace!.icon,
|
icon: widget.selectedSpace!.icon,
|
||||||
projectTags: widget.projectTags,
|
projectTags: widget.projectTags,
|
||||||
parentSpace:
|
parentSpace: SpaceHelper.findSpaceByInternalId(
|
||||||
SpaceHelper.findSpaceByInternalId(widget.selectedSpace?.parent?.internalId, spaces),
|
widget.selectedSpace?.parent?.internalId, spaces),
|
||||||
editSpace: widget.selectedSpace,
|
editSpace: widget.selectedSpace,
|
||||||
currentSpaceModel: widget.selectedSpace?.spaceModel,
|
currentSpaceModel: widget.selectedSpace?.spaceModel,
|
||||||
tags: widget.selectedSpace?.tags,
|
tags: widget.selectedSpace?.tags,
|
||||||
subspaces: widget.selectedSpace?.subspaces,
|
subspaces: widget.selectedSpace?.subspaces,
|
||||||
isEdit: true,
|
isEdit: true,
|
||||||
allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
|
allTags: TagHelper.getAllTagValues(
|
||||||
onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts,
|
widget.communities, widget.spaceModels),
|
||||||
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) {
|
onCreateSpace: (String name,
|
||||||
|
String icon,
|
||||||
|
List<SelectedProduct> selectedProducts,
|
||||||
|
SpaceTemplateModel? spaceModel,
|
||||||
|
List<SubspaceModel>? subspaces,
|
||||||
|
List<Tag>? tags) {
|
||||||
setState(() {
|
setState(() {
|
||||||
// Update the space's properties
|
// Update the space's properties
|
||||||
widget.selectedSpace!.name = name;
|
widget.selectedSpace!.name = name;
|
||||||
@ -379,7 +393,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
widget.selectedSpace!.tags = tags;
|
widget.selectedSpace!.tags = tags;
|
||||||
|
|
||||||
if (widget.selectedSpace!.status != SpaceStatus.newSpace) {
|
if (widget.selectedSpace!.status != SpaceStatus.newSpace) {
|
||||||
widget.selectedSpace!.status = SpaceStatus.modified; // Mark as modified
|
widget.selectedSpace!.status =
|
||||||
|
SpaceStatus.modified; // Mark as modified
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var space in spaces) {
|
for (var space in spaces) {
|
||||||
@ -410,7 +425,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
Map<String, SpaceModel> idToSpace = {};
|
Map<String, SpaceModel> idToSpace = {};
|
||||||
|
|
||||||
void flatten(SpaceModel space) {
|
void flatten(SpaceModel space) {
|
||||||
if (space.status == SpaceStatus.deleted || space.status == SpaceStatus.parentDeleted) {
|
if (space.status == SpaceStatus.deleted ||
|
||||||
|
space.status == SpaceStatus.parentDeleted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
result.add(space);
|
result.add(space);
|
||||||
@ -447,7 +463,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
Connection(
|
Connection(
|
||||||
startSpace: parent,
|
startSpace: parent,
|
||||||
endSpace: child,
|
endSpace: child,
|
||||||
direction: "down",
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -532,13 +547,16 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
|
|
||||||
void _selectSpace(BuildContext context, SpaceModel space) {
|
void _selectSpace(BuildContext context, SpaceModel space) {
|
||||||
context.read<SpaceManagementBloc>().add(
|
context.read<SpaceManagementBloc>().add(
|
||||||
SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: space),
|
SelectSpaceEvent(
|
||||||
|
selectedCommunity: widget.selectedCommunity,
|
||||||
|
selectedSpace: space),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _deselectSpace(BuildContext context) {
|
void _deselectSpace(BuildContext context) {
|
||||||
context.read<SpaceManagementBloc>().add(
|
context.read<SpaceManagementBloc>().add(
|
||||||
SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: null),
|
SelectSpaceEvent(
|
||||||
|
selectedCommunity: widget.selectedCommunity, selectedSpace: null),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -708,7 +726,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
|
|
||||||
SpaceModel duplicated = _deepCloneSpaceTree(space, parent: parent);
|
SpaceModel duplicated = _deepCloneSpaceTree(space, parent: parent);
|
||||||
|
|
||||||
duplicated.position = Offset(space.position.dx + 300, space.position.dy + 100);
|
duplicated.position =
|
||||||
|
Offset(space.position.dx + 300, space.position.dy + 100);
|
||||||
List<SpaceModel> duplicatedSubtree = [];
|
List<SpaceModel> duplicatedSubtree = [];
|
||||||
void collectSubtree(SpaceModel node) {
|
void collectSubtree(SpaceModel node) {
|
||||||
duplicatedSubtree.add(node);
|
duplicatedSubtree.add(node);
|
||||||
@ -726,7 +745,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
final newConnection = Connection(
|
final newConnection = Connection(
|
||||||
startSpace: parent,
|
startSpace: parent,
|
||||||
endSpace: duplicated,
|
endSpace: duplicated,
|
||||||
direction: "down",
|
|
||||||
);
|
);
|
||||||
connections.add(newConnection);
|
connections.add(newConnection);
|
||||||
duplicated.incomingConnection = newConnection;
|
duplicated.incomingConnection = newConnection;
|
||||||
@ -739,7 +757,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SpaceModel _deepCloneSpaceTree(SpaceModel original, {SpaceModel? parent}) {
|
SpaceModel _deepCloneSpaceTree(SpaceModel original, {SpaceModel? parent}) {
|
||||||
final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces);
|
final duplicatedName =
|
||||||
|
SpaceHelper.generateUniqueSpaceName(original.name, spaces);
|
||||||
|
|
||||||
final newSpace = SpaceModel(
|
final newSpace = SpaceModel(
|
||||||
name: duplicatedName,
|
name: duplicatedName,
|
||||||
@ -761,7 +780,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
final newConnection = Connection(
|
final newConnection = Connection(
|
||||||
startSpace: newSpace,
|
startSpace: newSpace,
|
||||||
endSpace: duplicatedChild,
|
endSpace: duplicatedChild,
|
||||||
direction: "down",
|
|
||||||
);
|
);
|
||||||
connections.add(newConnection);
|
connections.add(newConnection);
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,101 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
|
||||||
|
import '../../../../../common/edit_chip.dart';
|
||||||
|
import '../../../../../utils/color_manager.dart';
|
||||||
|
import '../../../helper/tag_helper.dart';
|
||||||
|
import '../../../space_model/widgets/button_content_widget.dart';
|
||||||
|
import '../../model/subspace_model.dart';
|
||||||
|
import '../../model/tag.dart';
|
||||||
|
|
||||||
|
class DevicesPartWidget extends StatelessWidget {
|
||||||
|
const DevicesPartWidget({
|
||||||
|
super.key,
|
||||||
|
required this.tags,
|
||||||
|
required this.subspaces,
|
||||||
|
required this.screenWidth,
|
||||||
|
required this.onEditChip,
|
||||||
|
required this.onTextButtonPressed,
|
||||||
|
required this.isTagsAndSubspaceModelDisabled,
|
||||||
|
});
|
||||||
|
final bool isTagsAndSubspaceModelDisabled;
|
||||||
|
final void Function() onEditChip;
|
||||||
|
final void Function() onTextButtonPressed;
|
||||||
|
final double screenWidth;
|
||||||
|
final List<Tag>? tags;
|
||||||
|
final List<SubspaceModel>? subspaces;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
(tags?.isNotEmpty == true ||
|
||||||
|
subspaces?.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
|
||||||
|
...TagHelper.groupTags([
|
||||||
|
...?tags,
|
||||||
|
...?subspaces?.expand((subspace) => subspace.tags ?? [])
|
||||||
|
]).entries.map(
|
||||||
|
(entry) => Chip(
|
||||||
|
avatar: SizedBox(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
entry.key.icon ?? 'assets/icons/gateway.svg',
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
label: Text(
|
||||||
|
'x${entry.value}', // Show count
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall
|
||||||
|
?.copyWith(color: ColorsManager.spaceColor),
|
||||||
|
),
|
||||||
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
side: const BorderSide(
|
||||||
|
color: ColorsManager.spaceColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
EditChip(onTap: onEditChip)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: TextButton(
|
||||||
|
onPressed: onTextButtonPressed,
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
child: ButtonContentWidget(
|
||||||
|
icon: Icons.add,
|
||||||
|
label: 'Add Devices',
|
||||||
|
disabled: isTagsAndSubspaceModelDisabled,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
|
||||||
|
import '../../../../../utils/color_manager.dart';
|
||||||
|
import '../../../../../utils/constants/assets.dart';
|
||||||
|
|
||||||
|
class IconChoosePartWidget extends StatelessWidget {
|
||||||
|
const IconChoosePartWidget({
|
||||||
|
super.key,
|
||||||
|
required this.selectedIcon,
|
||||||
|
required this.showIconSelection,
|
||||||
|
required this.screenWidth,
|
||||||
|
});
|
||||||
|
final double screenWidth;
|
||||||
|
final String selectedIcon;
|
||||||
|
final void Function() showIconSelection;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 50),
|
||||||
|
Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: screenWidth * 0.1,
|
||||||
|
height: screenWidth * 0.1,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: ColorsManager.boxColor,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SvgPicture.asset(
|
||||||
|
selectedIcon,
|
||||||
|
width: screenWidth * 0.04,
|
||||||
|
height: screenWidth * 0.04,
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 20,
|
||||||
|
right: 20,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: showIconSelection,
|
||||||
|
child: Container(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
Assets.iconEdit,
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,89 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../../../../utils/color_manager.dart';
|
||||||
|
import '../../../../../utils/constants/assets.dart';
|
||||||
|
import '../../../space_model/models/space_template_model.dart';
|
||||||
|
import '../../../space_model/widgets/button_content_widget.dart';
|
||||||
|
|
||||||
|
class SpaceModelLinkingWidget extends StatelessWidget {
|
||||||
|
const SpaceModelLinkingWidget({
|
||||||
|
super.key,
|
||||||
|
required this.onDeleted,
|
||||||
|
required this.onPressed,
|
||||||
|
required this.screenWidth,
|
||||||
|
required this.selectedSpaceModel,
|
||||||
|
required this.isSpaceModelDisabled,
|
||||||
|
});
|
||||||
|
final bool isSpaceModelDisabled;
|
||||||
|
final void Function()? onDeleted;
|
||||||
|
final void Function()? onPressed;
|
||||||
|
final double screenWidth;
|
||||||
|
final SpaceTemplateModel? selectedSpaceModel;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
selectedSpaceModel == null
|
||||||
|
? TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
onPressed: onPressed,
|
||||||
|
child: ButtonContentWidget(
|
||||||
|
svgAssets: Assets.link,
|
||||||
|
label: 'Link a space model',
|
||||||
|
disabled: isSpaceModelDisabled,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Container(
|
||||||
|
width: screenWidth * 0.25,
|
||||||
|
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: [
|
||||||
|
Chip(
|
||||||
|
label: Text(
|
||||||
|
selectedSpaceModel?.modelName ?? '',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium!
|
||||||
|
.copyWith(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: onDeleted),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../../../../utils/color_manager.dart';
|
||||||
|
|
||||||
|
class SpaceNameTextfieldWidget extends StatelessWidget {
|
||||||
|
SpaceNameTextfieldWidget({
|
||||||
|
super.key,
|
||||||
|
required this.isNameFieldExist,
|
||||||
|
required this.isNameFieldInvalid,
|
||||||
|
required this.onChange,
|
||||||
|
required this.screenWidth,
|
||||||
|
required this.nameController,
|
||||||
|
});
|
||||||
|
TextEditingController nameController;
|
||||||
|
final void Function(String value) onChange;
|
||||||
|
final double screenWidth;
|
||||||
|
bool isNameFieldExist;
|
||||||
|
bool isNameFieldInvalid;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: screenWidth * 0.25,
|
||||||
|
child: TextField(
|
||||||
|
controller: nameController,
|
||||||
|
onChanged: onChange,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Please enter the name',
|
||||||
|
hintStyle: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium!
|
||||||
|
.copyWith(color: ColorsManager.lightGrayColor),
|
||||||
|
filled: true,
|
||||||
|
fillColor: ColorsManager.boxColor,
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: isNameFieldInvalid || isNameFieldExist
|
||||||
|
? ColorsManager.red
|
||||||
|
: ColorsManager.boxColor,
|
||||||
|
width: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
borderSide: const BorderSide(
|
||||||
|
color: ColorsManager.boxColor,
|
||||||
|
width: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isNameFieldInvalid)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
|
child: Text(
|
||||||
|
'*Space name should not be empty.',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall
|
||||||
|
?.copyWith(color: ColorsManager.red),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isNameFieldExist)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
|
child: Text(
|
||||||
|
'*Name already exist',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall
|
||||||
|
?.copyWith(color: ColorsManager.red),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,101 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../../../../common/edit_chip.dart';
|
||||||
|
import '../../../../../utils/color_manager.dart';
|
||||||
|
import '../../../space_model/widgets/button_content_widget.dart';
|
||||||
|
import '../../../space_model/widgets/subspace_name_label_widget.dart';
|
||||||
|
import '../../model/subspace_model.dart';
|
||||||
|
|
||||||
|
class SubSpacePartWidget extends StatefulWidget {
|
||||||
|
SubSpacePartWidget({
|
||||||
|
super.key,
|
||||||
|
required this.subspaces,
|
||||||
|
required this.onPressed,
|
||||||
|
required this.isTagsAndSubspaceModelDisabled,
|
||||||
|
required this.screenWidth,
|
||||||
|
required this.editChipOnTap,
|
||||||
|
});
|
||||||
|
double screenWidth;
|
||||||
|
bool isTagsAndSubspaceModelDisabled;
|
||||||
|
final void Function() editChipOnTap;
|
||||||
|
List<SubspaceModel>? subspaces;
|
||||||
|
final void Function() onPressed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SubSpacePartWidget> createState() => _SubSpacePartWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SubSpacePartWidgetState extends State<SubSpacePartWidget> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
widget.subspaces == null || widget.subspaces!.isEmpty
|
||||||
|
? TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
overlayColor: ColorsManager.transparentColor,
|
||||||
|
),
|
||||||
|
onPressed: widget.onPressed,
|
||||||
|
child: ButtonContentWidget(
|
||||||
|
icon: Icons.add,
|
||||||
|
label: 'Create Sub Spaces',
|
||||||
|
disabled: widget.isTagsAndSubspaceModelDisabled,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SizedBox(
|
||||||
|
width: widget.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: [
|
||||||
|
if (widget.subspaces != null)
|
||||||
|
...widget.subspaces!.map((subspace) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SubspaceNameDisplayWidget(
|
||||||
|
text: subspace.subspaceName,
|
||||||
|
validateName: (updatedName) {
|
||||||
|
bool nameExists = widget.subspaces!.any((s) {
|
||||||
|
bool isSameId =
|
||||||
|
s.internalId == subspace.internalId;
|
||||||
|
bool isSameName =
|
||||||
|
s.subspaceName.trim().toLowerCase() ==
|
||||||
|
updatedName.trim().toLowerCase();
|
||||||
|
|
||||||
|
return !isSameId && isSameName;
|
||||||
|
});
|
||||||
|
|
||||||
|
return !nameExists;
|
||||||
|
},
|
||||||
|
onNameChanged: (updatedName) {
|
||||||
|
setState(() {
|
||||||
|
subspace.subspaceName = updatedName;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
EditChip(
|
||||||
|
onTap: widget.editChipOnTap,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -30,27 +30,12 @@ class CurvedLinePainter extends CustomPainter {
|
|||||||
Offset end = connection.endSpace.position +
|
Offset end = connection.endSpace.position +
|
||||||
const Offset(75, 0); // Center top of end space
|
const Offset(75, 0); // Center top of end space
|
||||||
|
|
||||||
if (connection.direction == 'down') {
|
// Curved line for down connections
|
||||||
// Curved line for down connections
|
final controlPoint = Offset((start.dx + end.dx) / 2, start.dy + 50);
|
||||||
final controlPoint = Offset((start.dx + end.dx) / 2, start.dy + 50);
|
final path = Path()
|
||||||
final path = Path()
|
..moveTo(start.dx, start.dy)
|
||||||
..moveTo(start.dx, start.dy)
|
..quadraticBezierTo(controlPoint.dx, controlPoint.dy, end.dx, end.dy);
|
||||||
..quadraticBezierTo(controlPoint.dx, controlPoint.dy, end.dx, end.dy);
|
canvas.drawPath(path, paint);
|
||||||
canvas.drawPath(path, paint);
|
|
||||||
} else if (connection.direction == 'right') {
|
|
||||||
start = connection.startSpace.position +
|
|
||||||
const Offset(150, 30); // Right center
|
|
||||||
end = connection.endSpace.position + const Offset(0, 30); // Left center
|
|
||||||
|
|
||||||
canvas.drawLine(start, end, paint);
|
|
||||||
} else if (connection.direction == 'left') {
|
|
||||||
start =
|
|
||||||
connection.startSpace.position + const Offset(0, 30); // Left center
|
|
||||||
end = connection.endSpace.position +
|
|
||||||
const Offset(150, 30); // Right center
|
|
||||||
|
|
||||||
canvas.drawLine(start, end, paint);
|
|
||||||
}
|
|
||||||
|
|
||||||
final dotPaint = Paint()..color = ColorsManager.blackColor;
|
final dotPaint = Paint()..color = ColorsManager.blackColor;
|
||||||
canvas.drawCircle(start, 5, dotPaint); // Start dot
|
canvas.drawCircle(start, 5, dotPaint); // Start dot
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
|
||||||
import 'package:syncrow_web/common/edit_chip.dart';
|
|
||||||
import 'package:syncrow_web/pages/common/buttons/cancel_button.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/common/buttons/default_button.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_widget.dart';
|
import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_widget.dart';
|
||||||
@ -9,6 +7,11 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_pr
|
|||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/devices_part_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/icon_choose_part_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/space_model_linking_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/space_name_textfield_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/create_space_widgets/sub_space_part_widget.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart';
|
import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart';
|
import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart';
|
||||||
@ -16,8 +19,6 @@ import 'package:syncrow_web/pages/spaces_management/helper/space_helper.dart';
|
|||||||
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
|
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart';
|
import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart';
|
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
import 'package:syncrow_web/utils/constants/space_icon_const.dart';
|
import 'package:syncrow_web/utils/constants/space_icon_const.dart';
|
||||||
@ -82,8 +83,10 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
selectedIcon = widget.icon ?? Assets.location;
|
selectedIcon = widget.icon ?? Assets.location;
|
||||||
nameController = TextEditingController(text: widget.name ?? '');
|
nameController = TextEditingController(text: widget.name ?? '');
|
||||||
selectedProducts = widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
|
selectedProducts =
|
||||||
isOkButtonEnabled = enteredName.isNotEmpty || nameController.text.isNotEmpty;
|
widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
|
||||||
|
isOkButtonEnabled =
|
||||||
|
enteredName.isNotEmpty || nameController.text.isNotEmpty;
|
||||||
if (widget.currentSpaceModel != null) {
|
if (widget.currentSpaceModel != null) {
|
||||||
subspaces = [];
|
subspaces = [];
|
||||||
tags = [];
|
tags = [];
|
||||||
@ -96,13 +99,15 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
bool isSpaceModelDisabled =
|
bool isSpaceModelDisabled = (tags != null && tags!.isNotEmpty ||
|
||||||
(tags != null && tags!.isNotEmpty || subspaces != null && subspaces!.isNotEmpty);
|
subspaces != null && subspaces!.isNotEmpty);
|
||||||
bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null);
|
bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null);
|
||||||
|
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: widget.isEdit ? const Text('Edit Space') : const Text('Create New Space'),
|
title: widget.isEdit
|
||||||
|
? const Text('Edit Space')
|
||||||
|
: const Text('Create New Space'),
|
||||||
backgroundColor: ColorsManager.whiteColors,
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
width: screenWidth * 0.5,
|
width: screenWidth * 0.5,
|
||||||
@ -112,50 +117,10 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
child: Column(
|
child: IconChoosePartWidget(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
selectedIcon: selectedIcon,
|
||||||
// crossAxisAlignment: CrossAxisAlignment.center,
|
showIconSelection: _showIconSelectionDialog,
|
||||||
children: [
|
screenWidth: screenWidth,
|
||||||
const SizedBox(height: 50),
|
|
||||||
Stack(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: screenWidth * 0.1,
|
|
||||||
height: screenWidth * 0.1,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: ColorsManager.boxColor,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SvgPicture.asset(
|
|
||||||
selectedIcon,
|
|
||||||
width: screenWidth * 0.04,
|
|
||||||
height: screenWidth * 0.04,
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
top: 20,
|
|
||||||
right: 20,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: _showIconSelectionDialog,
|
|
||||||
child: Container(
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: SvgPicture.asset(
|
|
||||||
Assets.iconEdit,
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 20),
|
const SizedBox(width: 20),
|
||||||
@ -164,342 +129,146 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SpaceNameTextfieldWidget(
|
||||||
width: screenWidth * 0.25,
|
isNameFieldExist: isNameFieldExist,
|
||||||
child: TextField(
|
isNameFieldInvalid: isNameFieldInvalid,
|
||||||
controller: nameController,
|
nameController: nameController,
|
||||||
onChanged: (value) {
|
screenWidth: screenWidth,
|
||||||
enteredName = value.trim();
|
onChange: (value) {
|
||||||
setState(() {
|
enteredName = value.trim();
|
||||||
isNameFieldExist = false;
|
setState(() {
|
||||||
isOkButtonEnabled = false;
|
isNameFieldExist = false;
|
||||||
isNameFieldInvalid = value.isEmpty;
|
isOkButtonEnabled = false;
|
||||||
|
isNameFieldInvalid = value.isEmpty;
|
||||||
|
|
||||||
if (!isNameFieldInvalid) {
|
if (!isNameFieldInvalid) {
|
||||||
if (SpaceHelper.isNameConflict(
|
if (SpaceHelper.isNameConflict(
|
||||||
value, widget.parentSpace, widget.editSpace)) {
|
value, widget.parentSpace, widget.editSpace)) {
|
||||||
isNameFieldExist = true;
|
isNameFieldExist = true;
|
||||||
isOkButtonEnabled = false;
|
isOkButtonEnabled = false;
|
||||||
} else {
|
} else {
|
||||||
isNameFieldExist = false;
|
isNameFieldExist = false;
|
||||||
isOkButtonEnabled = true;
|
isOkButtonEnabled = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
},
|
});
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
},
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: 'Please enter the name',
|
|
||||||
hintStyle: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodyMedium!
|
|
||||||
.copyWith(color: ColorsManager.lightGrayColor),
|
|
||||||
filled: true,
|
|
||||||
fillColor: ColorsManager.boxColor,
|
|
||||||
enabledBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
borderSide: BorderSide(
|
|
||||||
color: isNameFieldInvalid || isNameFieldExist
|
|
||||||
? ColorsManager.red
|
|
||||||
: ColorsManager.boxColor,
|
|
||||||
width: 1.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
focusedBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
borderSide: const BorderSide(
|
|
||||||
color: ColorsManager.boxColor,
|
|
||||||
width: 1.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (isNameFieldInvalid)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
|
||||||
child: Text(
|
|
||||||
'*Space name should not be empty.',
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodySmall
|
|
||||||
?.copyWith(color: ColorsManager.red),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (isNameFieldExist)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
|
||||||
child: Text(
|
|
||||||
'*Name already exist',
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodySmall
|
|
||||||
?.copyWith(color: ColorsManager.red),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
selectedSpaceModel == null
|
// SpaceModelLinkingWidget(
|
||||||
? TextButton(
|
// isSpaceModelDisabled: true,
|
||||||
style: TextButton.styleFrom(
|
// // isSpaceModelDisabled,
|
||||||
padding: EdgeInsets.zero,
|
// onPressed: () {
|
||||||
),
|
// isSpaceModelDisabled
|
||||||
onPressed: () {
|
// ? null
|
||||||
isSpaceModelDisabled ? null : _showLinkSpaceModelDialog(context);
|
// : _showLinkSpaceModelDialog(context);
|
||||||
},
|
// },
|
||||||
child: ButtonContentWidget(
|
// onDeleted: () => setState(() {
|
||||||
svgAssets: Assets.link,
|
// selectedSpaceModel = null;
|
||||||
label: 'Link a space model',
|
// subspaces = widget.subspaces ?? [];
|
||||||
disabled: isSpaceModelDisabled,
|
// tags = widget.tags ?? [];
|
||||||
),
|
// }),
|
||||||
)
|
// screenWidth: screenWidth,
|
||||||
: Container(
|
// selectedSpaceModel: selectedSpaceModel,
|
||||||
width: screenWidth * 0.25,
|
// ),
|
||||||
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: [
|
|
||||||
Chip(
|
|
||||||
label: Text(
|
|
||||||
selectedSpaceModel?.modelName ?? '',
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodyMedium!
|
|
||||||
.copyWith(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: () => setState(() {
|
|
||||||
this.selectedSpaceModel = null;
|
|
||||||
subspaces = widget.subspaces ?? [];
|
|
||||||
tags = widget.tags ?? [];
|
|
||||||
})),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 25),
|
const SizedBox(height: 25),
|
||||||
Row(
|
// Row(
|
||||||
children: [
|
// children: [
|
||||||
const Expanded(
|
// const Expanded(
|
||||||
child: Divider(
|
// child: Divider(
|
||||||
color: ColorsManager.neutralGray,
|
// color: ColorsManager.neutralGray,
|
||||||
thickness: 1.0,
|
// thickness: 1.0,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
Padding(
|
// Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
// padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||||
child: Text(
|
// child: Text(
|
||||||
'OR',
|
// 'OR',
|
||||||
style: Theme.of(context)
|
// style: Theme.of(context)
|
||||||
.textTheme
|
// .textTheme
|
||||||
.bodyMedium
|
// .bodyMedium
|
||||||
?.copyWith(fontWeight: FontWeight.bold),
|
// ?.copyWith(fontWeight: FontWeight.bold),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
const Expanded(
|
// const Expanded(
|
||||||
child: Divider(
|
// child: Divider(
|
||||||
color: ColorsManager.neutralGray,
|
// color: ColorsManager.neutralGray,
|
||||||
thickness: 1.0,
|
// thickness: 1.0,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
|
// ),
|
||||||
|
const SizedBox(height: 25),
|
||||||
|
SubSpacePartWidget(
|
||||||
|
subspaces: subspaces,
|
||||||
|
onPressed: () {
|
||||||
|
isTagsAndSubspaceModelDisabled
|
||||||
|
? null
|
||||||
|
: _showSubSpaceDialog(
|
||||||
|
context,
|
||||||
|
enteredName,
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
widget.products,
|
||||||
|
subspaces,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isTagsAndSubspaceModelDisabled:
|
||||||
|
isTagsAndSubspaceModelDisabled,
|
||||||
|
screenWidth: screenWidth,
|
||||||
|
editChipOnTap: () async {
|
||||||
|
_showSubSpaceDialog(
|
||||||
|
context,
|
||||||
|
enteredName,
|
||||||
|
[],
|
||||||
|
true,
|
||||||
|
widget.products,
|
||||||
|
subspaces,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 25),
|
|
||||||
subspaces == null || subspaces!.isEmpty
|
|
||||||
? TextButton(
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
overlayColor: ColorsManager.transparentColor,
|
|
||||||
),
|
|
||||||
onPressed: () async {
|
|
||||||
isTagsAndSubspaceModelDisabled
|
|
||||||
? null
|
|
||||||
: _showSubSpaceDialog(
|
|
||||||
context, enteredName, [], false, widget.products, subspaces);
|
|
||||||
},
|
|
||||||
child: ButtonContentWidget(
|
|
||||||
icon: Icons.add,
|
|
||||||
label: 'Create Sub Space',
|
|
||||||
disabled: isTagsAndSubspaceModelDisabled,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: 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: [
|
|
||||||
if (subspaces != null)
|
|
||||||
...subspaces!.map((subspace) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
SubspaceNameDisplayWidget(
|
|
||||||
text: subspace.subspaceName,
|
|
||||||
validateName: (updatedName) {
|
|
||||||
bool nameExists = subspaces!.any((s) {
|
|
||||||
bool isSameId = s.internalId == subspace.internalId;
|
|
||||||
bool isSameName =
|
|
||||||
s.subspaceName.trim().toLowerCase() ==
|
|
||||||
updatedName.trim().toLowerCase();
|
|
||||||
|
|
||||||
return !isSameId && isSameName;
|
|
||||||
});
|
|
||||||
|
|
||||||
return !nameExists;
|
|
||||||
},
|
|
||||||
onNameChanged: (updatedName) {
|
|
||||||
setState(() {
|
|
||||||
subspace.subspaceName = updatedName;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
EditChip(
|
|
||||||
onTap: () async {
|
|
||||||
_showSubSpaceDialog(context, enteredName, [], true,
|
|
||||||
widget.products, subspaces);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
(tags?.isNotEmpty == true ||
|
DevicesPartWidget(
|
||||||
subspaces?.any((subspace) => subspace.tags?.isNotEmpty == true) == true)
|
tags: tags,
|
||||||
? SizedBox(
|
subspaces: subspaces,
|
||||||
width: screenWidth * 0.25,
|
screenWidth: screenWidth,
|
||||||
child: Container(
|
isTagsAndSubspaceModelDisabled:
|
||||||
padding: const EdgeInsets.all(8.0),
|
isTagsAndSubspaceModelDisabled,
|
||||||
decoration: BoxDecoration(
|
onEditChip: () async {
|
||||||
color: ColorsManager.textFieldGreyColor,
|
await showDialog(
|
||||||
borderRadius: BorderRadius.circular(15),
|
context: context,
|
||||||
border: Border.all(
|
builder: (context) => AssignTagDialog(
|
||||||
color: ColorsManager.textFieldGreyColor,
|
products: widget.products,
|
||||||
width: 3.0, // Border width
|
subspaces: subspaces,
|
||||||
),
|
allTags: widget.allTags,
|
||||||
),
|
addedProducts:
|
||||||
child: Wrap(
|
TagHelper.createInitialSelectedProductsForTags(
|
||||||
spacing: 8.0,
|
tags ?? [], subspaces),
|
||||||
runSpacing: 8.0,
|
title: 'Edit Device',
|
||||||
children: [
|
initialTags: TagHelper.generateInitialForTags(
|
||||||
// Combine tags from spaceModel and subspaces
|
spaceTags: tags, subspaces: subspaces),
|
||||||
...TagHelper.groupTags([
|
spaceName: widget.name ?? '',
|
||||||
...?tags,
|
projectTags: widget.projectTags,
|
||||||
...?subspaces?.expand((subspace) => subspace.tags ?? [])
|
onSave: (updatedTags, updatedSubspaces) {
|
||||||
]).entries.map(
|
setState(() {
|
||||||
(entry) => Chip(
|
tags = updatedTags;
|
||||||
avatar: SizedBox(
|
subspaces = updatedSubspaces;
|
||||||
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: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodySmall
|
|
||||||
?.copyWith(color: ColorsManager.spaceColor),
|
|
||||||
),
|
|
||||||
backgroundColor: ColorsManager.whiteColors,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
side: const BorderSide(
|
|
||||||
color: ColorsManager.spaceColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
EditChip(onTap: () async {
|
|
||||||
await showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AssignTagDialog(
|
|
||||||
products: widget.products,
|
|
||||||
subspaces: subspaces,
|
|
||||||
allTags: widget.allTags,
|
|
||||||
addedProducts:
|
|
||||||
TagHelper.createInitialSelectedProductsForTags(
|
|
||||||
tags ?? [], subspaces),
|
|
||||||
title: 'Edit Device',
|
|
||||||
initialTags: TagHelper.generateInitialForTags(
|
|
||||||
spaceTags: tags, subspaces: subspaces),
|
|
||||||
spaceName: widget.name ?? '',
|
|
||||||
projectTags: widget.projectTags,
|
|
||||||
onSave: (updatedTags, updatedSubspaces) {
|
|
||||||
setState(() {
|
|
||||||
tags = updatedTags;
|
|
||||||
subspaces = updatedSubspaces;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
})
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
isTagsAndSubspaceModelDisabled
|
|
||||||
? null
|
|
||||||
: _showTagCreateDialog(
|
|
||||||
context,
|
|
||||||
enteredName,
|
|
||||||
widget.isEdit,
|
|
||||||
widget.products,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
style: TextButton.styleFrom(
|
),
|
||||||
padding: EdgeInsets.zero,
|
);
|
||||||
),
|
},
|
||||||
child: ButtonContentWidget(
|
onTextButtonPressed: () {
|
||||||
icon: Icons.add,
|
isTagsAndSubspaceModelDisabled
|
||||||
label: 'Add Devices',
|
? null
|
||||||
disabled: isTagsAndSubspaceModelDisabled,
|
: _showTagCreateDialog(
|
||||||
))
|
context,
|
||||||
|
enteredName,
|
||||||
|
widget.isEdit,
|
||||||
|
widget.products,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -529,17 +298,32 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
} else if (isNameFieldExist) {
|
} else if (isNameFieldExist) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? '');
|
String newName = enteredName.isNotEmpty
|
||||||
|
? enteredName
|
||||||
|
: (widget.name ?? '');
|
||||||
if (newName.isNotEmpty) {
|
if (newName.isNotEmpty) {
|
||||||
widget.onCreateSpace(newName, selectedIcon, selectedProducts,
|
if (tags != null && tags!.isNotEmpty) {
|
||||||
selectedSpaceModel, subspaces, tags);
|
if (tags!.any(
|
||||||
|
(tag) => tag.uuid == null || tag.uuid!.isEmpty,
|
||||||
|
)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
widget.onCreateSpace(
|
||||||
|
newName,
|
||||||
|
selectedIcon,
|
||||||
|
selectedProducts,
|
||||||
|
selectedSpaceModel,
|
||||||
|
subspaces,
|
||||||
|
tags);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
backgroundColor:
|
backgroundColor: isOkButtonEnabled
|
||||||
isOkButtonEnabled ? ColorsManager.secondaryColor : ColorsManager.grayColor,
|
? ColorsManager.secondaryColor
|
||||||
|
: ColorsManager.grayColor,
|
||||||
foregroundColor: ColorsManager.whiteColors,
|
foregroundColor: ColorsManager.whiteColors,
|
||||||
child: const Text('OK'),
|
child: const Text('OK'),
|
||||||
),
|
),
|
||||||
@ -550,6 +334,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//dialooogggs
|
||||||
void _showIconSelectionDialog() {
|
void _showIconSelectionDialog() {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@ -586,26 +371,50 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showSubSpaceDialog(BuildContext context, String name, final List<Tag>? spaceTags,
|
void _showSubSpaceDialog(
|
||||||
bool isEdit, List<ProductModel>? products, final List<SubspaceModel>? existingSubSpaces) {
|
BuildContext context,
|
||||||
|
String name,
|
||||||
|
final List<Tag>? spaceTags,
|
||||||
|
bool isEdit,
|
||||||
|
List<ProductModel>? products,
|
||||||
|
final List<SubspaceModel>? existingSubSpaces,
|
||||||
|
) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return CreateSubSpaceDialog(
|
return CreateSubSpaceDialog(
|
||||||
spaceName: name,
|
spaceName: name,
|
||||||
dialogTitle: isEdit ? 'Edit Sub-space' : 'Create Sub-space',
|
dialogTitle: isEdit ? 'Edit Sub-spaces' : 'Create Sub-spaces',
|
||||||
products: products,
|
products: products,
|
||||||
existingSubSpaces: existingSubSpaces,
|
existingSubSpaces: existingSubSpaces,
|
||||||
onSave: (slectedSubspaces) {
|
onSave: (slectedSubspaces, updatedSubSpaces) {
|
||||||
final List<Tag> tagsToAppendToSpace = [];
|
final List<Tag> tagsToAppendToSpace = [];
|
||||||
|
|
||||||
if (slectedSubspaces != null) {
|
if (slectedSubspaces != null && slectedSubspaces.isNotEmpty) {
|
||||||
final updatedIds = slectedSubspaces.map((s) => s.internalId).toSet();
|
final updatedIds =
|
||||||
|
slectedSubspaces.map((s) => s.internalId).toSet();
|
||||||
if (existingSubSpaces != null) {
|
if (existingSubSpaces != null) {
|
||||||
final deletedSubspaces =
|
final deletedSubspaces = existingSubSpaces
|
||||||
existingSubSpaces.where((s) => !updatedIds.contains(s.internalId)).toList();
|
.where((s) => !updatedIds.contains(s.internalId))
|
||||||
|
.toList();
|
||||||
for (var s in deletedSubspaces) {
|
for (var s in deletedSubspaces) {
|
||||||
if (s.tags != null) {
|
if (s.tags != null) {
|
||||||
|
s.tags!.forEach(
|
||||||
|
(tag) => tag.location = null,
|
||||||
|
);
|
||||||
|
tagsToAppendToSpace.addAll(s.tags!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (existingSubSpaces != null) {
|
||||||
|
final deletedSubspaces = existingSubSpaces;
|
||||||
|
|
||||||
|
for (var s in deletedSubspaces) {
|
||||||
|
if (s.tags != null) {
|
||||||
|
s.tags!.forEach(
|
||||||
|
(tag) => tag.location = null,
|
||||||
|
);
|
||||||
tagsToAppendToSpace.addAll(s.tags!);
|
tagsToAppendToSpace.addAll(s.tags!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -623,15 +432,16 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showTagCreateDialog(
|
void _showTagCreateDialog(BuildContext context, String name, bool isEdit,
|
||||||
BuildContext context, String name, bool isEdit, List<ProductModel>? products) {
|
List<ProductModel>? products) {
|
||||||
isEdit
|
isEdit
|
||||||
? showDialog(
|
? showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AssignTagDialog(
|
return AssignTagDialog(
|
||||||
title: 'Edit Device',
|
title: 'Edit Device',
|
||||||
addedProducts: TagHelper.createInitialSelectedProductsForTags(tags, subspaces),
|
addedProducts: TagHelper.createInitialSelectedProductsForTags(
|
||||||
|
tags, subspaces),
|
||||||
spaceName: name,
|
spaceName: name,
|
||||||
products: products,
|
products: products,
|
||||||
subspaces: subspaces,
|
subspaces: subspaces,
|
||||||
@ -646,7 +456,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
if (subspaces != null) {
|
if (subspaces != null) {
|
||||||
for (final subspace in subspaces!) {
|
for (final subspace in subspaces!) {
|
||||||
for (final selectedSubspace in selectedSubspaces) {
|
for (final selectedSubspace in selectedSubspaces) {
|
||||||
if (subspace.subspaceName == selectedSubspace.subspaceName) {
|
if (subspace.subspaceName ==
|
||||||
|
selectedSubspace.subspaceName) {
|
||||||
subspace.tags = selectedSubspace.tags;
|
subspace.tags = selectedSubspace.tags;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -670,7 +481,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
allTags: widget.allTags,
|
allTags: widget.allTags,
|
||||||
projectTags: widget.projectTags,
|
projectTags: widget.projectTags,
|
||||||
initialSelectedProducts:
|
initialSelectedProducts:
|
||||||
TagHelper.createInitialSelectedProductsForTags(tags, subspaces),
|
TagHelper.createInitialSelectedProductsForTags(
|
||||||
|
tags, subspaces),
|
||||||
onSave: (selectedSpaceTags, selectedSubspaces) {
|
onSave: (selectedSpaceTags, selectedSubspaces) {
|
||||||
setState(() {
|
setState(() {
|
||||||
tags = selectedSpaceTags;
|
tags = selectedSpaceTags;
|
||||||
@ -680,7 +492,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
if (subspaces != null) {
|
if (subspaces != null) {
|
||||||
for (final subspace in subspaces!) {
|
for (final subspace in subspaces!) {
|
||||||
for (final selectedSubspace in selectedSubspaces) {
|
for (final selectedSubspace in selectedSubspaces) {
|
||||||
if (subspace.subspaceName == selectedSubspace.subspaceName) {
|
if (subspace.subspaceName ==
|
||||||
|
selectedSubspace.subspaceName) {
|
||||||
subspace.tags = selectedSubspace.tags;
|
subspace.tags = selectedSubspace.tags;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ class PlusButtonWidget extends StatelessWidget {
|
|||||||
final int index;
|
final int index;
|
||||||
final String direction;
|
final String direction;
|
||||||
final Offset offset;
|
final Offset offset;
|
||||||
final Function(int index, Offset newPosition, String direction) onButtonTap;
|
final Function(int index, Offset newPosition) onButtonTap;
|
||||||
|
|
||||||
const PlusButtonWidget({
|
const PlusButtonWidget({
|
||||||
super.key,
|
super.key,
|
||||||
@ -17,35 +17,21 @@ class PlusButtonWidget extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Positioned(
|
return GestureDetector(
|
||||||
left: offset.dx,
|
onTap: () {
|
||||||
top: offset.dy,
|
onButtonTap(index, const Offset(0, 150));
|
||||||
child: GestureDetector(
|
},
|
||||||
onTap: () {
|
child: Container(
|
||||||
Offset newPosition;
|
width: 30,
|
||||||
switch (direction) {
|
height: 30,
|
||||||
case 'left':
|
decoration: const BoxDecoration(
|
||||||
newPosition = const Offset(-200, 0);
|
color: ColorsManager.spaceColor,
|
||||||
break;
|
shape: BoxShape.circle,
|
||||||
case 'right':
|
),
|
||||||
newPosition = const Offset(200, 0);
|
child: const Icon(
|
||||||
break;
|
Icons.add,
|
||||||
case 'down':
|
color: ColorsManager.whiteColors,
|
||||||
newPosition = const Offset(0, 150);
|
size: 20,
|
||||||
break;
|
|
||||||
default:
|
|
||||||
newPosition = Offset.zero;
|
|
||||||
}
|
|
||||||
onButtonTap(index, newPosition, direction);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
width: 30,
|
|
||||||
height: 30,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: ColorsManager.spaceColor,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: const Icon(Icons.add, color: ColorsManager.whiteColors, size: 20),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -7,7 +7,7 @@ class SpaceCardWidget extends StatelessWidget {
|
|||||||
final Offset position;
|
final Offset position;
|
||||||
final bool isHovered;
|
final bool isHovered;
|
||||||
final Function(int index, bool isHovered) onHoverChanged;
|
final Function(int index, bool isHovered) onHoverChanged;
|
||||||
final Function(int index, Offset newPosition, String direction) onButtonTap;
|
final Function(int index, Offset newPosition) onButtonTap;
|
||||||
final Widget Function(int index) buildSpaceContainer;
|
final Widget Function(int index) buildSpaceContainer;
|
||||||
final ValueChanged<Offset> onPositionChanged;
|
final ValueChanged<Offset> onPositionChanged;
|
||||||
|
|
||||||
@ -25,35 +25,34 @@ class SpaceCardWidget extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
return MouseRegion(
|
||||||
behavior: HitTestBehavior.opaque,
|
onEnter: (_) => onHoverChanged(index, true),
|
||||||
onPanUpdate: (details) {
|
onExit: (_) => onHoverChanged(index, false),
|
||||||
// Call the provided callback to update the position
|
child: SizedBox(
|
||||||
final newPosition = position + details.delta;
|
width: 140, // Make sure this covers both card and plus button
|
||||||
onPositionChanged(newPosition);
|
height: 90,
|
||||||
},
|
|
||||||
child: MouseRegion(
|
|
||||||
onEnter: (_) {
|
|
||||||
// Call the provided callback to handle hover state
|
|
||||||
onHoverChanged(index, true);
|
|
||||||
},
|
|
||||||
onExit: (_) {
|
|
||||||
// Call the provided callback to handle hover state
|
|
||||||
onHoverChanged(index, false);
|
|
||||||
},
|
|
||||||
child: Stack(
|
child: Stack(
|
||||||
clipBehavior: Clip
|
clipBehavior: Clip.none,
|
||||||
.none, // Allow hovering elements to be displayed outside the boundary
|
|
||||||
children: [
|
children: [
|
||||||
buildSpaceContainer(index), // Build the space container
|
// Main card
|
||||||
if (isHovered) ...[
|
Container(
|
||||||
PlusButtonWidget(
|
width: 140,
|
||||||
index: index,
|
height: 80,
|
||||||
direction: 'down',
|
alignment: Alignment.center,
|
||||||
offset: const Offset(63, 50),
|
color: Colors.transparent,
|
||||||
onButtonTap: onButtonTap,
|
child: buildSpaceContainer(index),
|
||||||
|
),
|
||||||
|
// Plus button (NO inner Positioned!)
|
||||||
|
if (isHovered)
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: PlusButtonWidget(
|
||||||
|
index: index,
|
||||||
|
direction: 'down',
|
||||||
|
offset: Offset.zero,
|
||||||
|
onButtonTap: onButtonTap,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -13,7 +13,8 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
|||||||
final existingTagCounts = <String, int>{};
|
final existingTagCounts = <String, int>{};
|
||||||
for (var tag in initialTags) {
|
for (var tag in initialTags) {
|
||||||
if (tag.product != null) {
|
if (tag.product != null) {
|
||||||
existingTagCounts[tag.product!.uuid] = (existingTagCounts[tag.product!.uuid] ?? 0) + 1;
|
existingTagCounts[tag.product!.uuid] =
|
||||||
|
(existingTagCounts[tag.product!.uuid] ?? 0) + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,14 +23,17 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
|||||||
for (var selectedProduct in event.addedProducts) {
|
for (var selectedProduct in event.addedProducts) {
|
||||||
final existingCount = existingTagCounts[selectedProduct.productId] ?? 0;
|
final existingCount = existingTagCounts[selectedProduct.productId] ?? 0;
|
||||||
|
|
||||||
if (selectedProduct.count == 0 || selectedProduct.count <= existingCount) {
|
if (selectedProduct.count == 0 ||
|
||||||
tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
|
selectedProduct.count <= existingCount) {
|
||||||
|
tags.addAll(initialTags
|
||||||
|
.where((tag) => tag.product?.uuid == selectedProduct.productId));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final missingCount = selectedProduct.count - existingCount;
|
final missingCount = selectedProduct.count - existingCount;
|
||||||
|
|
||||||
tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
|
tags.addAll(initialTags
|
||||||
|
.where((tag) => tag.product?.uuid == selectedProduct.productId));
|
||||||
|
|
||||||
if (missingCount > 0) {
|
if (missingCount > 0) {
|
||||||
tags.addAll(List.generate(
|
tags.addAll(List.generate(
|
||||||
@ -85,7 +89,8 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
|||||||
final tags = List<Tag>.from(currentState.tags);
|
final tags = List<Tag>.from(currentState.tags);
|
||||||
|
|
||||||
// Update the location
|
// Update the location
|
||||||
tags[event.index] = tags[event.index].copyWith(location: event.location);
|
tags[event.index] =
|
||||||
|
tags[event.index].copyWith(location: event.location);
|
||||||
|
|
||||||
final updatedTags = _calculateAvailableTags(projectTags, tags);
|
final updatedTags = _calculateAvailableTags(projectTags, tags);
|
||||||
|
|
||||||
@ -117,7 +122,8 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
|||||||
final currentState = state;
|
final currentState = state;
|
||||||
|
|
||||||
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
|
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
|
||||||
final tags = List<Tag>.from(currentState.tags)..remove(event.tagToDelete);
|
final tags = List<Tag>.from(currentState.tags)
|
||||||
|
..remove(event.tagToDelete);
|
||||||
|
|
||||||
// Recalculate available tags
|
// Recalculate available tags
|
||||||
final updatedTags = _calculateAvailableTags(projectTags, tags);
|
final updatedTags = _calculateAvailableTags(projectTags, tags);
|
||||||
@ -141,8 +147,10 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
|||||||
|
|
||||||
// Get validation error for duplicate tags
|
// Get validation error for duplicate tags
|
||||||
String? _getValidationError(List<Tag> tags) {
|
String? _getValidationError(List<Tag> tags) {
|
||||||
final nonEmptyTags =
|
final nonEmptyTags = tags
|
||||||
tags.map((tag) => tag.tag?.trim() ?? '').where((tag) => tag.isNotEmpty).toList();
|
.map((tag) => tag.tag?.trim() ?? '')
|
||||||
|
.where((tag) => tag.isNotEmpty)
|
||||||
|
.toList();
|
||||||
|
|
||||||
final duplicateTags = nonEmptyTags
|
final duplicateTags = nonEmptyTags
|
||||||
.fold<Map<String, int>>({}, (map, tag) {
|
.fold<Map<String, int>>({}, (map, tag) {
|
||||||
@ -168,9 +176,11 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
|||||||
.toSet();
|
.toSet();
|
||||||
|
|
||||||
final availableTags = allTags
|
final availableTags = allTags
|
||||||
.where((tag) => tag.tag != null && !selectedTagSet.contains(tag.tag!.trim()))
|
.where((tag) =>
|
||||||
|
tag.tag != null && !selectedTagSet.contains(tag.tag!.trim()))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
return availableTags;
|
return projectTags;
|
||||||
|
// availableTags;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/common/dialog_dropdown.dart';
|
|
||||||
import 'package:syncrow_web/common/tag_dialog_textfield_dropdown.dart';
|
|
||||||
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
|
|
||||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
|
||||||
import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_widget.dart';
|
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||||
@ -12,9 +7,10 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
|||||||
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart';
|
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart';
|
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart';
|
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
|
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/widgets/assign_tags_tables_widget.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
|
||||||
|
import 'widgets/save_add_device_row_widget.dart';
|
||||||
|
|
||||||
class AssignTagDialog extends StatelessWidget {
|
class AssignTagDialog extends StatelessWidget {
|
||||||
final List<ProductModel>? products;
|
final List<ProductModel>? products;
|
||||||
@ -29,7 +25,7 @@ class AssignTagDialog extends StatelessWidget {
|
|||||||
final List<Tag> projectTags;
|
final List<Tag> projectTags;
|
||||||
|
|
||||||
const AssignTagDialog(
|
const AssignTagDialog(
|
||||||
{Key? key,
|
{super.key,
|
||||||
required this.products,
|
required this.products,
|
||||||
required this.subspaces,
|
required this.subspaces,
|
||||||
required this.addedProducts,
|
required this.addedProducts,
|
||||||
@ -39,13 +35,14 @@ class AssignTagDialog extends StatelessWidget {
|
|||||||
required this.spaceName,
|
required this.spaceName,
|
||||||
required this.title,
|
required this.title,
|
||||||
this.onSave,
|
this.onSave,
|
||||||
required this.projectTags})
|
required this.projectTags});
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final List<String> locations =
|
final List<String> locations = (subspaces ?? [])
|
||||||
(subspaces ?? []).map((subspace) => subspace.subspaceName).toList()..add('Main Space');
|
.map((subspace) => subspace.subspaceName)
|
||||||
|
.toList()
|
||||||
|
..add('Main Space');
|
||||||
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (_) => AssignTagBloc(projectTags)
|
create: (_) => AssignTagBloc(projectTags)
|
||||||
@ -67,131 +64,31 @@ class AssignTagDialog extends StatelessWidget {
|
|||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
AssignTagsTable(
|
||||||
borderRadius: BorderRadius.circular(20),
|
controllers: controllers,
|
||||||
child: DataTable(
|
locations: locations,
|
||||||
headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey),
|
tags: state.tags,
|
||||||
key: ValueKey(state.tags.length),
|
updatedTags: state.updatedTags,
|
||||||
border: TableBorder.all(
|
onDeleteDevice: ({required index, required tag}) {
|
||||||
color: ColorsManager.dataHeaderGrey,
|
context
|
||||||
width: 1,
|
.read<AssignTagBloc>()
|
||||||
borderRadius: BorderRadius.circular(20),
|
.add(DeleteTag(tagToDelete: tag, tags: state.tags));
|
||||||
),
|
|
||||||
columns: [
|
|
||||||
DataColumn(
|
|
||||||
label: Text('#', style: Theme.of(context).textTheme.bodyMedium)),
|
|
||||||
DataColumn(
|
|
||||||
label: Text('Device', style: Theme.of(context).textTheme.bodyMedium)),
|
|
||||||
DataColumn(
|
|
||||||
numeric: false,
|
|
||||||
label: Text('Tag', style: Theme.of(context).textTheme.bodyMedium)),
|
|
||||||
DataColumn(
|
|
||||||
label:
|
|
||||||
Text('Location', style: Theme.of(context).textTheme.bodyMedium)),
|
|
||||||
],
|
|
||||||
rows: state.tags.isEmpty
|
|
||||||
? [
|
|
||||||
DataRow(cells: [
|
|
||||||
DataCell(
|
|
||||||
Center(
|
|
||||||
child: Text('No Data Available',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
||||||
color: ColorsManager.lightGrayColor,
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const DataCell(SizedBox()),
|
|
||||||
const DataCell(SizedBox()),
|
|
||||||
const DataCell(SizedBox()),
|
|
||||||
])
|
|
||||||
]
|
|
||||||
: List.generate(state.tags.length, (index) {
|
|
||||||
final tag = state.tags[index];
|
|
||||||
final controller = controllers[index];
|
|
||||||
|
|
||||||
return DataRow(
|
controllers.removeAt(index);
|
||||||
cells: [
|
},
|
||||||
DataCell(Text((index + 1).toString())),
|
onTagDropDownSelected: ({required index, required tag}) {
|
||||||
DataCell(
|
context.read<AssignTagBloc>().add(UpdateTagEvent(
|
||||||
Row(
|
index: index,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
tag: tag,
|
||||||
children: [
|
));
|
||||||
Expanded(
|
},
|
||||||
child: Text(
|
onLocationDropDownSelected: (
|
||||||
tag.product?.name ?? 'Unknown',
|
{required index, required location}) {
|
||||||
overflow: TextOverflow.ellipsis,
|
context.read<AssignTagBloc>().add(UpdateLocation(
|
||||||
)),
|
index: index,
|
||||||
const SizedBox(width: 10),
|
location: location,
|
||||||
Container(
|
));
|
||||||
width: 20.0,
|
},
|
||||||
height: 20.0,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(
|
|
||||||
color: ColorsManager.lightGrayColor,
|
|
||||||
width: 1.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.close,
|
|
||||||
color: ColorsManager.lightGreyColor,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
context.read<AssignTagBloc>().add(
|
|
||||||
DeleteTag(tagToDelete: tag, tags: state.tags));
|
|
||||||
|
|
||||||
controllers.removeAt(index);
|
|
||||||
},
|
|
||||||
tooltip: 'Delete Tag',
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
constraints: const BoxConstraints(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
DataCell(
|
|
||||||
Container(
|
|
||||||
alignment:
|
|
||||||
Alignment.centerLeft, // Align cell content to the left
|
|
||||||
child: SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: TagDialogTextfieldDropdown(
|
|
||||||
key: ValueKey('dropdown_${const Uuid().v4()}_$index'),
|
|
||||||
items: state.updatedTags,
|
|
||||||
product: tag.product?.uuid ?? 'Unknown',
|
|
||||||
initialValue: tag,
|
|
||||||
onSelected: (value) {
|
|
||||||
controller.text = value.tag ?? '';
|
|
||||||
context.read<AssignTagBloc>().add(UpdateTagEvent(
|
|
||||||
index: index,
|
|
||||||
tag: value,
|
|
||||||
));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
DataCell(
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: DialogDropdown(
|
|
||||||
items: locations,
|
|
||||||
selectedValue: tag.location ?? 'Main Space',
|
|
||||||
onSelected: (value) {
|
|
||||||
context.read<AssignTagBloc>().add(UpdateLocation(
|
|
||||||
index: index,
|
|
||||||
location: value,
|
|
||||||
));
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (state.errorMessage != null)
|
if (state.errorMessage != null)
|
||||||
Text(state.errorMessage!,
|
Text(state.errorMessage!,
|
||||||
@ -203,69 +100,15 @@ class AssignTagDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
Row(
|
SaveAddDeviceRowWidget(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
subspaces: subspaces,
|
||||||
children: [
|
products: products,
|
||||||
const SizedBox(width: 10),
|
projectTags: projectTags,
|
||||||
Expanded(
|
spaceName: spaceName,
|
||||||
child: Builder(
|
onSave: onSave,
|
||||||
builder: (buttonContext) => CancelButton(
|
allTags: allTags,
|
||||||
label: 'Add New Device',
|
tags: state.tags,
|
||||||
onPressed: () async {
|
isSaveEnabled: state.isSaveEnabled,
|
||||||
final updatedTags = List<Tag>.from(state.tags);
|
|
||||||
final result = TagHelper.processTags(updatedTags, subspaces);
|
|
||||||
|
|
||||||
final processedTags = result['updatedTags'] as List<Tag>;
|
|
||||||
final processedSubspaces =
|
|
||||||
List<SubspaceModel>.from(result['subspaces'] as List<dynamic>);
|
|
||||||
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
|
|
||||||
await showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AddDeviceTypeWidget(
|
|
||||||
products: products,
|
|
||||||
subspaces: processedSubspaces,
|
|
||||||
projectTags: projectTags,
|
|
||||||
initialSelectedProducts:
|
|
||||||
TagHelper.createInitialSelectedProductsForTags(
|
|
||||||
processedTags, processedSubspaces),
|
|
||||||
spaceName: spaceName,
|
|
||||||
spaceTags: processedTags,
|
|
||||||
isCreate: false,
|
|
||||||
onSave: onSave,
|
|
||||||
allTags: allTags,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Expanded(
|
|
||||||
child: DefaultButton(
|
|
||||||
borderRadius: 10,
|
|
||||||
backgroundColor: ColorsManager.secondaryColor,
|
|
||||||
foregroundColor: state.isSaveEnabled
|
|
||||||
? ColorsManager.whiteColors
|
|
||||||
: ColorsManager.whiteColorsWithOpacity,
|
|
||||||
onPressed: state.isSaveEnabled
|
|
||||||
? () async {
|
|
||||||
final updatedTags = List<Tag>.from(state.tags);
|
|
||||||
final result = TagHelper.processTags(updatedTags, subspaces);
|
|
||||||
|
|
||||||
final processedTags = result['updatedTags'] as List<Tag>;
|
|
||||||
final processedSubspaces =
|
|
||||||
List<SubspaceModel>.from(result['subspaces'] as List<dynamic>);
|
|
||||||
onSave?.call(processedTags, processedSubspaces);
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
child: const Text('Save'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,100 @@
|
|||||||
|
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/all_spaces/model/subspace_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.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/utils/color_manager.dart';
|
||||||
|
|
||||||
|
import '../../../add_device_type/views/add_device_type_widget.dart';
|
||||||
|
import '../../../helper/tag_helper.dart';
|
||||||
|
|
||||||
|
class SaveAddDeviceRowWidget extends StatelessWidget {
|
||||||
|
const SaveAddDeviceRowWidget({
|
||||||
|
super.key,
|
||||||
|
required this.subspaces,
|
||||||
|
required this.products,
|
||||||
|
required this.projectTags,
|
||||||
|
required this.spaceName,
|
||||||
|
required this.onSave,
|
||||||
|
required this.allTags,
|
||||||
|
required this.tags,
|
||||||
|
required this.isSaveEnabled,
|
||||||
|
});
|
||||||
|
final List<Tag> tags;
|
||||||
|
final List<SubspaceModel>? subspaces;
|
||||||
|
final List<ProductModel>? products;
|
||||||
|
final List<Tag> projectTags;
|
||||||
|
final String spaceName;
|
||||||
|
final Function(List<Tag> p1, List<SubspaceModel>? p2)? onSave;
|
||||||
|
final List<String>? allTags;
|
||||||
|
final bool isSaveEnabled;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Builder(
|
||||||
|
builder: (buttonContext) => CancelButton(
|
||||||
|
label: 'Add New Device',
|
||||||
|
onPressed: () async {
|
||||||
|
final updatedTags = List<Tag>.from(tags);
|
||||||
|
final result = TagHelper.processTags(updatedTags, subspaces);
|
||||||
|
|
||||||
|
final processedTags = result['updatedTags'] as List<Tag>;
|
||||||
|
final processedSubspaces = List<SubspaceModel>.from(
|
||||||
|
result['subspaces'] as List<dynamic>);
|
||||||
|
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
|
await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AddDeviceTypeWidget(
|
||||||
|
products: products,
|
||||||
|
subspaces: processedSubspaces,
|
||||||
|
projectTags: projectTags,
|
||||||
|
initialSelectedProducts:
|
||||||
|
TagHelper.createInitialSelectedProductsForTags(
|
||||||
|
processedTags, processedSubspaces),
|
||||||
|
spaceName: spaceName,
|
||||||
|
spaceTags: processedTags,
|
||||||
|
isCreate: false,
|
||||||
|
onSave: onSave,
|
||||||
|
allTags: allTags,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: DefaultButton(
|
||||||
|
borderRadius: 10,
|
||||||
|
backgroundColor: ColorsManager.secondaryColor,
|
||||||
|
foregroundColor: isSaveEnabled
|
||||||
|
? ColorsManager.whiteColors
|
||||||
|
: ColorsManager.whiteColorsWithOpacity,
|
||||||
|
onPressed: isSaveEnabled
|
||||||
|
? () async {
|
||||||
|
final updatedTags = List<Tag>.from(tags);
|
||||||
|
final result =
|
||||||
|
TagHelper.processTags(updatedTags, subspaces);
|
||||||
|
|
||||||
|
final processedTags = result['updatedTags'] as List<Tag>;
|
||||||
|
final processedSubspaces = List<SubspaceModel>.from(
|
||||||
|
result['subspaces'] as List<dynamic>);
|
||||||
|
onSave?.call(processedTags, processedSubspaces);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
child: const Text('Save'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,9 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/common/dialog_dropdown.dart';
|
|
||||||
import 'package:syncrow_web/common/tag_dialog_textfield_dropdown.dart';
|
|
||||||
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
|
|
||||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||||
@ -12,11 +8,10 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assig
|
|||||||
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.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/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/subspace_template_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart';
|
|
||||||
import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
|
|
||||||
import 'package:uuid/uuid.dart';
|
import 'widgets/RowOfCancelSaveWidget.dart';
|
||||||
|
import 'widgets/assign_tags_tables_widget.dart';
|
||||||
|
|
||||||
class AssignTagModelsDialog extends StatelessWidget {
|
class AssignTagModelsDialog extends StatelessWidget {
|
||||||
final List<ProductModel>? products;
|
final List<ProductModel>? products;
|
||||||
@ -53,8 +48,10 @@ class AssignTagModelsDialog extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final List<String> locations =
|
final List<String> locations = (subspaces ?? [])
|
||||||
(subspaces ?? []).map((subspace) => subspace.subspaceName).toList()..add('Main Space');
|
.map((subspace) => subspace.subspaceName)
|
||||||
|
.toList()
|
||||||
|
..add('Main Space');
|
||||||
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (_) => AssignTagModelBloc(projectTags)
|
create: (_) => AssignTagModelBloc(projectTags)
|
||||||
@ -78,137 +75,38 @@ class AssignTagModelsDialog extends StatelessWidget {
|
|||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
AssignTagsTable(
|
||||||
borderRadius: BorderRadius.circular(20),
|
controllers: controllers,
|
||||||
child: DataTable(
|
locations: locations,
|
||||||
headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey),
|
tags: state.tags,
|
||||||
key: ValueKey(state.tags.length),
|
updatedTags: state.updatedTags,
|
||||||
border: TableBorder.all(
|
onDeleteDevice: ({required index, required tag}) {
|
||||||
color: ColorsManager.dataHeaderGrey,
|
context
|
||||||
width: 1,
|
.read<AssignTagModelBloc>()
|
||||||
borderRadius: BorderRadius.circular(20),
|
.add(DeleteTagModel(
|
||||||
),
|
tagToDelete: tag,
|
||||||
columns: [
|
tags: state.tags,
|
||||||
DataColumn(
|
));
|
||||||
label: Text('#', style: Theme.of(context).textTheme.bodyMedium)),
|
controllers.removeAt(index);
|
||||||
DataColumn(
|
},
|
||||||
label: Text('Device',
|
onTagDropDownSelected: (
|
||||||
style: Theme.of(context).textTheme.bodyMedium)),
|
{required index, required tag}) {
|
||||||
DataColumn(
|
context.read<AssignTagModelBloc>().add(
|
||||||
numeric: false,
|
UpdateTag(
|
||||||
label:
|
index: index,
|
||||||
Text('Tag', style: Theme.of(context).textTheme.bodyMedium)),
|
tag: tag,
|
||||||
DataColumn(
|
),
|
||||||
label: Text('Location',
|
);
|
||||||
style: Theme.of(context).textTheme.bodyMedium)),
|
},
|
||||||
],
|
onLocationDropDownSelected: (
|
||||||
rows: state.tags.isEmpty
|
{required index, required location}) {
|
||||||
? [
|
context.read<AssignTagModelBloc>().add(
|
||||||
DataRow(cells: [
|
UpdateLocation(
|
||||||
DataCell(
|
index: index,
|
||||||
Center(
|
location: location,
|
||||||
child: Text('No Devices Available',
|
),
|
||||||
style:
|
);
|
||||||
Theme.of(context).textTheme.bodyMedium?.copyWith(
|
},
|
||||||
color: ColorsManager.lightGrayColor,
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const DataCell(SizedBox()),
|
|
||||||
const DataCell(SizedBox()),
|
|
||||||
const DataCell(SizedBox()),
|
|
||||||
])
|
|
||||||
]
|
|
||||||
: List.generate(state.tags.length, (index) {
|
|
||||||
final tag = state.tags[index];
|
|
||||||
final controller = controllers[index];
|
|
||||||
|
|
||||||
return DataRow(
|
|
||||||
cells: [
|
|
||||||
DataCell(Text((index + 1).toString())),
|
|
||||||
DataCell(
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
tag.product?.name ?? 'Unknown',
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
)),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Container(
|
|
||||||
width: 20.0,
|
|
||||||
height: 20.0,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(
|
|
||||||
color: ColorsManager.lightGrayColor,
|
|
||||||
width: 1.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.close,
|
|
||||||
color: ColorsManager.lightGreyColor,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
context.read<AssignTagModelBloc>().add(
|
|
||||||
DeleteTagModel(
|
|
||||||
tagToDelete: tag, tags: state.tags));
|
|
||||||
controllers.removeAt(index);
|
|
||||||
},
|
|
||||||
tooltip: 'Delete Tag',
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
constraints: const BoxConstraints(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
DataCell(
|
|
||||||
Container(
|
|
||||||
alignment: Alignment
|
|
||||||
.centerLeft, // Align cell content to the left
|
|
||||||
child: SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: TagDialogTextfieldDropdown(
|
|
||||||
key: ValueKey(
|
|
||||||
'dropdown_${const Uuid().v4()}_$index'),
|
|
||||||
product: tag.product?.uuid ?? 'Unknown',
|
|
||||||
items: state.updatedTags,
|
|
||||||
initialValue: tag,
|
|
||||||
onSelected: (value) {
|
|
||||||
controller.text = value.tag ?? '';
|
|
||||||
context.read<AssignTagModelBloc>().add(UpdateTag(
|
|
||||||
index: index,
|
|
||||||
tag: value,
|
|
||||||
));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
DataCell(
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: DialogDropdown(
|
|
||||||
items: locations,
|
|
||||||
selectedValue: tag.location ?? 'Main Space',
|
|
||||||
onSelected: (value) {
|
|
||||||
context
|
|
||||||
.read<AssignTagModelBloc>()
|
|
||||||
.add(UpdateLocation(
|
|
||||||
index: index,
|
|
||||||
location: value,
|
|
||||||
));
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (state.errorMessage != null)
|
if (state.errorMessage != null)
|
||||||
Text(state.errorMessage!,
|
Text(state.errorMessage!,
|
||||||
@ -220,101 +118,16 @@ class AssignTagModelsDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
Row(
|
RowOfSaveCancelWidget(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
subspaces: subspaces,
|
||||||
children: [
|
products: products,
|
||||||
const SizedBox(width: 10),
|
allTags: allTags,
|
||||||
Expanded(
|
spaceName: spaceName,
|
||||||
child: Builder(
|
otherSpaceModels: otherSpaceModels,
|
||||||
builder: (buttonContext) => CancelButton(
|
pageContext: pageContext,
|
||||||
label: 'Add New Device',
|
projectTags: projectTags,
|
||||||
onPressed: () async {
|
spaceModel: spaceModel,
|
||||||
final updatedTags = List<Tag>.from(state.tags);
|
allSpaceModels: allSpaceModels,
|
||||||
final result =
|
|
||||||
TagHelper.updateSubspaceTagModels(updatedTags, subspaces);
|
|
||||||
|
|
||||||
final processedTags = result['updatedTags'] as List<Tag>;
|
|
||||||
final processedSubspaces = List<SubspaceTemplateModel>.from(
|
|
||||||
result['subspaces'] as List<dynamic>);
|
|
||||||
|
|
||||||
if (context.mounted) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
|
|
||||||
await showDialog<bool>(
|
|
||||||
barrierDismissible: false,
|
|
||||||
context: context,
|
|
||||||
builder: (dialogContext) => AddDeviceTypeModelWidget(
|
|
||||||
products: products,
|
|
||||||
subspaces: processedSubspaces,
|
|
||||||
isCreate: false,
|
|
||||||
initialSelectedProducts:
|
|
||||||
TagHelper.createInitialSelectedProducts(
|
|
||||||
processedTags, processedSubspaces),
|
|
||||||
allTags: allTags,
|
|
||||||
spaceName: spaceName,
|
|
||||||
otherSpaceModels: otherSpaceModels,
|
|
||||||
spaceTagModels: processedTags,
|
|
||||||
pageContext: pageContext,
|
|
||||||
projectTags: projectTags,
|
|
||||||
spaceModel: SpaceTemplateModel(
|
|
||||||
modelName: spaceName,
|
|
||||||
tags: updatedTags,
|
|
||||||
uuid: spaceModel?.uuid,
|
|
||||||
internalId: spaceModel?.internalId,
|
|
||||||
subspaceModels: processedSubspaces)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Expanded(
|
|
||||||
child: DefaultButton(
|
|
||||||
borderRadius: 10,
|
|
||||||
backgroundColor: ColorsManager.secondaryColor,
|
|
||||||
foregroundColor: state.isSaveEnabled
|
|
||||||
? ColorsManager.whiteColors
|
|
||||||
: ColorsManager.whiteColorsWithOpacity,
|
|
||||||
onPressed: state.isSaveEnabled
|
|
||||||
? () async {
|
|
||||||
final updatedTags = List<Tag>.from(state.tags);
|
|
||||||
|
|
||||||
final result =
|
|
||||||
TagHelper.updateSubspaceTagModels(updatedTags, subspaces);
|
|
||||||
|
|
||||||
final processedTags = result['updatedTags'] as List<Tag>;
|
|
||||||
final processedSubspaces = List<SubspaceTemplateModel>.from(
|
|
||||||
result['subspaces'] as List<dynamic>);
|
|
||||||
|
|
||||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
|
||||||
|
|
||||||
await showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext dialogContext) {
|
|
||||||
return CreateSpaceModelDialog(
|
|
||||||
products: products,
|
|
||||||
allSpaceModels: allSpaceModels,
|
|
||||||
allTags: allTags,
|
|
||||||
projectTags: projectTags,
|
|
||||||
pageContext: pageContext,
|
|
||||||
otherSpaceModels: otherSpaceModels,
|
|
||||||
spaceModel: SpaceTemplateModel(
|
|
||||||
modelName: spaceName,
|
|
||||||
tags: processedTags,
|
|
||||||
uuid: spaceModel?.uuid,
|
|
||||||
internalId: spaceModel?.internalId,
|
|
||||||
subspaceModels: processedSubspaces),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
child: const Text('Save'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,152 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import '../../../../../utils/color_manager.dart';
|
||||||
|
import '../../../../common/buttons/cancel_button.dart';
|
||||||
|
import '../../../../common/buttons/default_button.dart';
|
||||||
|
import '../../../all_spaces/model/product_model.dart';
|
||||||
|
import '../../../all_spaces/model/tag.dart';
|
||||||
|
import '../../../helper/tag_helper.dart';
|
||||||
|
import '../../../space_model/models/space_template_model.dart';
|
||||||
|
import '../../../space_model/models/subspace_template_model.dart';
|
||||||
|
import '../../../space_model/widgets/dialog/create_space_model_dialog.dart';
|
||||||
|
import '../../../tag_model/views/add_device_type_model_widget.dart';
|
||||||
|
import '../../bloc/assign_tag_model_bloc.dart';
|
||||||
|
import '../../bloc/assign_tag_model_state.dart';
|
||||||
|
|
||||||
|
class RowOfSaveCancelWidget extends StatelessWidget {
|
||||||
|
const RowOfSaveCancelWidget({
|
||||||
|
super.key,
|
||||||
|
required this.subspaces,
|
||||||
|
required this.products,
|
||||||
|
required this.allTags,
|
||||||
|
required this.spaceName,
|
||||||
|
required this.otherSpaceModels,
|
||||||
|
required this.pageContext,
|
||||||
|
required this.projectTags,
|
||||||
|
required this.spaceModel,
|
||||||
|
required this.allSpaceModels,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<SubspaceTemplateModel>? subspaces;
|
||||||
|
final List<ProductModel>? products;
|
||||||
|
final List<String>? allTags;
|
||||||
|
final String spaceName;
|
||||||
|
final List<String>? otherSpaceModels;
|
||||||
|
final BuildContext? pageContext;
|
||||||
|
final List<Tag> projectTags;
|
||||||
|
final SpaceTemplateModel? spaceModel;
|
||||||
|
final List<SpaceTemplateModel>? allSpaceModels;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<AssignTagModelBloc, AssignTagModelState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state is AssignTagModelLoaded) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Builder(
|
||||||
|
builder: (buttonContext) => CancelButton(
|
||||||
|
label: 'Add New Device',
|
||||||
|
onPressed: () async {
|
||||||
|
final updatedTags = List<Tag>.from(state.tags);
|
||||||
|
final result = TagHelper.updateSubspaceTagModels(
|
||||||
|
updatedTags, subspaces);
|
||||||
|
|
||||||
|
final processedTags = result['updatedTags'] as List<Tag>;
|
||||||
|
final processedSubspaces =
|
||||||
|
List<SubspaceTemplateModel>.from(
|
||||||
|
result['subspaces'] as List<dynamic>);
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
|
await showDialog<bool>(
|
||||||
|
barrierDismissible: false,
|
||||||
|
context: context,
|
||||||
|
builder: (dialogContext) => AddDeviceTypeModelWidget(
|
||||||
|
products: products,
|
||||||
|
subspaces: processedSubspaces,
|
||||||
|
isCreate: false,
|
||||||
|
initialSelectedProducts:
|
||||||
|
TagHelper.createInitialSelectedProducts(
|
||||||
|
processedTags, processedSubspaces),
|
||||||
|
allTags: allTags,
|
||||||
|
spaceName: spaceName,
|
||||||
|
otherSpaceModels: otherSpaceModels,
|
||||||
|
spaceTagModels: processedTags,
|
||||||
|
pageContext: pageContext,
|
||||||
|
projectTags: projectTags,
|
||||||
|
spaceModel: SpaceTemplateModel(
|
||||||
|
modelName: spaceName,
|
||||||
|
tags: updatedTags,
|
||||||
|
uuid: spaceModel?.uuid,
|
||||||
|
internalId: spaceModel?.internalId,
|
||||||
|
subspaceModels: processedSubspaces)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: DefaultButton(
|
||||||
|
borderRadius: 10,
|
||||||
|
backgroundColor: ColorsManager.secondaryColor,
|
||||||
|
foregroundColor: state.isSaveEnabled
|
||||||
|
? ColorsManager.whiteColors
|
||||||
|
: ColorsManager.whiteColorsWithOpacity,
|
||||||
|
onPressed: state.isSaveEnabled
|
||||||
|
? () async {
|
||||||
|
final updatedTags = List<Tag>.from(state.tags);
|
||||||
|
|
||||||
|
final result = TagHelper.updateSubspaceTagModels(
|
||||||
|
updatedTags, subspaces);
|
||||||
|
|
||||||
|
final processedTags =
|
||||||
|
result['updatedTags'] as List<Tag>;
|
||||||
|
final processedSubspaces =
|
||||||
|
List<SubspaceTemplateModel>.from(
|
||||||
|
result['subspaces'] as List<dynamic>);
|
||||||
|
|
||||||
|
Navigator.of(context)
|
||||||
|
.popUntil((route) => route.isFirst);
|
||||||
|
|
||||||
|
await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext dialogContext) {
|
||||||
|
return CreateSpaceModelDialog(
|
||||||
|
products: products,
|
||||||
|
allSpaceModels: allSpaceModels,
|
||||||
|
allTags: allTags,
|
||||||
|
projectTags: projectTags,
|
||||||
|
pageContext: pageContext,
|
||||||
|
otherSpaceModels: otherSpaceModels,
|
||||||
|
spaceModel: SpaceTemplateModel(
|
||||||
|
modelName: spaceName,
|
||||||
|
tags: processedTags,
|
||||||
|
uuid: spaceModel?.uuid,
|
||||||
|
internalId: spaceModel?.internalId,
|
||||||
|
subspaceModels: processedSubspaces),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
child: const Text('Save'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,155 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
import '../../../../../common/dialog_dropdown.dart';
|
||||||
|
import '../../../../../common/tag_dialog_textfield_dropdown.dart';
|
||||||
|
import '../../../../../utils/color_manager.dart';
|
||||||
|
|
||||||
|
class AssignTagsTable extends StatelessWidget {
|
||||||
|
const AssignTagsTable({
|
||||||
|
super.key,
|
||||||
|
required this.controllers,
|
||||||
|
required this.locations,
|
||||||
|
required this.tags,
|
||||||
|
required this.updatedTags,
|
||||||
|
required this.onDeleteDevice,
|
||||||
|
required this.onLocationDropDownSelected,
|
||||||
|
required this.onTagDropDownSelected,
|
||||||
|
});
|
||||||
|
final void Function({required Tag tag, required int index})
|
||||||
|
onTagDropDownSelected;
|
||||||
|
final void Function({required String location, required int index})
|
||||||
|
onLocationDropDownSelected;
|
||||||
|
final void Function({required Tag tag, required int index}) onDeleteDevice;
|
||||||
|
final List<Tag> tags;
|
||||||
|
final List<Tag> updatedTags;
|
||||||
|
final List<TextEditingController> controllers;
|
||||||
|
final List<String> locations;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
child: DataTable(
|
||||||
|
headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey),
|
||||||
|
key: ValueKey(tags.length),
|
||||||
|
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(
|
||||||
|
numeric: false,
|
||||||
|
label:
|
||||||
|
Text('Tag', style: Theme.of(context).textTheme.bodyMedium)),
|
||||||
|
DataColumn(
|
||||||
|
label: Text('Location',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium)),
|
||||||
|
],
|
||||||
|
rows: tags.isEmpty
|
||||||
|
? [
|
||||||
|
DataRow(cells: [
|
||||||
|
DataCell(
|
||||||
|
Center(
|
||||||
|
child: Text('No Devices Available',
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: ColorsManager.lightGrayColor,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const DataCell(SizedBox()),
|
||||||
|
const DataCell(SizedBox()),
|
||||||
|
const DataCell(SizedBox()),
|
||||||
|
])
|
||||||
|
]
|
||||||
|
: List.generate(tags.length, (index) {
|
||||||
|
final tag = tags[index];
|
||||||
|
final controller = controllers[index];
|
||||||
|
|
||||||
|
return DataRow(
|
||||||
|
cells: [
|
||||||
|
DataCell(Text((index + 1).toString())),
|
||||||
|
DataCell(
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
tag.product?.name ?? 'Unknown',
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
)),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Container(
|
||||||
|
width: 20.0,
|
||||||
|
height: 20.0,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorsManager.lightGrayColor,
|
||||||
|
width: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
color: ColorsManager.lightGreyColor,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
onDeleteDevice(tag: tag, index: index);
|
||||||
|
},
|
||||||
|
tooltip: 'Delete Tag',
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DataCell(
|
||||||
|
Container(
|
||||||
|
alignment: Alignment
|
||||||
|
.centerLeft, // Align cell content to the left
|
||||||
|
child: SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: TagDialogTextfieldDropdown(
|
||||||
|
key: ValueKey(
|
||||||
|
'dropdown_${const Uuid().v4()}_$index'),
|
||||||
|
product: tag.product?.uuid ?? 'Unknown',
|
||||||
|
items: updatedTags,
|
||||||
|
initialValue: tag,
|
||||||
|
onSelected: (value) {
|
||||||
|
controller.text = value.tag ?? '';
|
||||||
|
onTagDropDownSelected(tag: value, index: index);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DataCell(
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: DialogDropdown(
|
||||||
|
items: locations,
|
||||||
|
selectedValue: tag.location ?? 'Main Space',
|
||||||
|
onSelected: (value) {
|
||||||
|
onLocationDropDownSelected(
|
||||||
|
location: value, index: index);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -77,7 +77,7 @@ class SubSpaceBloc extends Bloc<SubSpaceEvent, SubSpaceState> {
|
|||||||
emit(SubSpaceState(
|
emit(SubSpaceState(
|
||||||
updatedSubSpaces,
|
updatedSubSpaces,
|
||||||
updatedSubspaceModels,
|
updatedSubspaceModels,
|
||||||
errorMessage,
|
errorMessage ,
|
||||||
updatedDuplicates,
|
updatedDuplicates,
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,23 +1,23 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.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/product_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart';
|
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_event.dart';
|
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_event.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_state.dart';
|
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_state.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/widgets/subspace_chip.dart';
|
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.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 'widgets/ok_cancel_sub_space_widget.dart';
|
||||||
|
import 'widgets/textfield_sub_space_dialog_widget.dart';
|
||||||
|
|
||||||
class CreateSubSpaceDialog extends StatefulWidget {
|
class CreateSubSpaceDialog extends StatefulWidget {
|
||||||
final String dialogTitle;
|
final String dialogTitle;
|
||||||
final List<SubspaceModel>? existingSubSpaces;
|
final List<SubspaceModel>? existingSubSpaces;
|
||||||
final String? spaceName;
|
final String? spaceName;
|
||||||
final List<ProductModel>? products;
|
final List<ProductModel>? products;
|
||||||
final void Function(List<SubspaceModel>?)? onSave;
|
final void Function(
|
||||||
|
List<SubspaceModel>?, List<UpdateSubspaceModel> updatedSubSpaces)? onSave;
|
||||||
|
|
||||||
const CreateSubSpaceDialog({
|
const CreateSubSpaceDialog({
|
||||||
required this.dialogTitle,
|
required this.dialogTitle,
|
||||||
@ -33,14 +33,7 @@ class CreateSubSpaceDialog extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _CreateSubSpaceDialogState extends State<CreateSubSpaceDialog> {
|
class _CreateSubSpaceDialogState extends State<CreateSubSpaceDialog> {
|
||||||
late final TextEditingController _subspaceNameController;
|
final TextEditingController _subspaceNameController = TextEditingController();
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
_subspaceNameController = TextEditingController();
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_subspaceNameController.dispose();
|
_subspaceNameController.dispose();
|
||||||
@ -79,83 +72,26 @@ class _CreateSubSpaceDialogState extends State<CreateSubSpaceDialog> {
|
|||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'press Enter to Save',
|
||||||
|
style: context.textTheme.headlineLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 10,
|
||||||
|
color: ColorsManager.grayColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 5,
|
||||||
|
),
|
||||||
|
const Icon(Icons.save_as_sharp, size: 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Container(
|
TextFieldSubSpaceDialogWidget(
|
||||||
width: context.screenWidth * 0.35,
|
subspaceNameController: _subspaceNameController,
|
||||||
padding: const EdgeInsets.symmetric(
|
subSpaces: state.subSpaces,
|
||||||
vertical: 10,
|
|
||||||
horizontal: 16,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: ColorsManager.boxColor,
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
child: Wrap(
|
|
||||||
spacing: 8,
|
|
||||||
runSpacing: 8,
|
|
||||||
alignment: WrapAlignment.start,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
|
||||||
children: [
|
|
||||||
...state.subSpaces.asMap().entries.map(
|
|
||||||
(entry) {
|
|
||||||
final index = entry.key;
|
|
||||||
final subSpace = entry.value;
|
|
||||||
|
|
||||||
final lowerName = subSpace.subspaceName.toLowerCase();
|
|
||||||
|
|
||||||
final duplicateIndices = state.subSpaces
|
|
||||||
.asMap()
|
|
||||||
.entries
|
|
||||||
.where((e) =>
|
|
||||||
e.value.subspaceName.toLowerCase() == lowerName)
|
|
||||||
.map((e) => e.key)
|
|
||||||
.toList();
|
|
||||||
final isDuplicate = duplicateIndices.length > 1 &&
|
|
||||||
duplicateIndices.indexOf(index) != 0;
|
|
||||||
return SubspaceChip(
|
|
||||||
subSpace: SubspaceTemplateModel(
|
|
||||||
subspaceName: entry.value.subspaceName,
|
|
||||||
disabled: entry.value.disabled,
|
|
||||||
),
|
|
||||||
isDuplicate: isDuplicate,
|
|
||||||
onDeleted: () => context.read<SubSpaceBloc>().add(
|
|
||||||
RemoveSubSpace(subSpace),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: 200,
|
|
||||||
child: TextField(
|
|
||||||
controller: _subspaceNameController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
border: InputBorder.none,
|
|
||||||
hintText: state.subSpaces.isEmpty
|
|
||||||
? 'Please enter the name'
|
|
||||||
: null,
|
|
||||||
hintStyle: context.textTheme.bodySmall?.copyWith(
|
|
||||||
color: ColorsManager.lightGrayColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onSubmitted: (value) {
|
|
||||||
final trimmedValue = value.trim();
|
|
||||||
if (trimmedValue.isNotEmpty) {
|
|
||||||
context.read<SubSpaceBloc>().add(
|
|
||||||
AddSubSpace(
|
|
||||||
SubspaceModel(
|
|
||||||
subspaceName: trimmedValue,
|
|
||||||
disabled: false,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
_subspaceNameController.clear();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
style: context.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (state.errorMessage.isNotEmpty)
|
if (state.errorMessage.isNotEmpty)
|
||||||
Padding(
|
Padding(
|
||||||
@ -168,36 +104,10 @@ class _CreateSubSpaceDialogState extends State<CreateSubSpaceDialog> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Row(
|
OkCancelSubSpaceWidget(
|
||||||
children: [
|
subspaceNameController: _subspaceNameController,
|
||||||
Expanded(
|
errorMessage: state.errorMessage,
|
||||||
child: CancelButton(
|
onSave: widget.onSave,
|
||||||
label: 'Cancel',
|
|
||||||
onPressed: () async {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Expanded(
|
|
||||||
child: DefaultButton(
|
|
||||||
onPressed: state.errorMessage.isEmpty
|
|
||||||
? () {
|
|
||||||
final subSpacesBloc = context.read<SubSpaceBloc>();
|
|
||||||
final subSpaces = subSpacesBloc.state.subSpaces;
|
|
||||||
widget.onSave?.call(subSpaces);
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
backgroundColor: ColorsManager.secondaryColor,
|
|
||||||
borderRadius: 10,
|
|
||||||
foregroundColor: state.errorMessage.isNotEmpty
|
|
||||||
? ColorsManager.whiteColorsWithOpacity
|
|
||||||
: ColorsManager.whiteColors,
|
|
||||||
child: const Text('OK'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -0,0 +1,75 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
|
||||||
|
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||||
|
|
||||||
|
import '../../bloc/subspace_bloc.dart';
|
||||||
|
import '../../bloc/subspace_event.dart';
|
||||||
|
|
||||||
|
class OkCancelSubSpaceWidget extends StatelessWidget {
|
||||||
|
const OkCancelSubSpaceWidget({
|
||||||
|
super.key,
|
||||||
|
required this.subspaceNameController,
|
||||||
|
required this.onSave,
|
||||||
|
required this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
final TextEditingController subspaceNameController;
|
||||||
|
final void Function(
|
||||||
|
List<SubspaceModel>?, List<UpdateSubspaceModel> updatedSubSpaces)? onSave;
|
||||||
|
final String errorMessage;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: CancelButton(
|
||||||
|
label: 'Cancel',
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: DefaultButton(
|
||||||
|
onPressed: errorMessage.isEmpty
|
||||||
|
? () async {
|
||||||
|
final trimmedValue = subspaceNameController.text.trim();
|
||||||
|
|
||||||
|
final subSpacesBloc = context.read<SubSpaceBloc>();
|
||||||
|
if (trimmedValue.isNotEmpty) {
|
||||||
|
subSpacesBloc.add(
|
||||||
|
AddSubSpace(
|
||||||
|
SubspaceModel(
|
||||||
|
subspaceName: trimmedValue,
|
||||||
|
disabled: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
subspaceNameController.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
await Future.delayed(const Duration(milliseconds: 10));
|
||||||
|
final subSpaces = subSpacesBloc.state.subSpaces;
|
||||||
|
|
||||||
|
onSave?.call(
|
||||||
|
subSpaces, subSpacesBloc.state.updatedSubSpaceModels);
|
||||||
|
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
backgroundColor: ColorsManager.secondaryColor,
|
||||||
|
borderRadius: 10,
|
||||||
|
foregroundColor: errorMessage.isNotEmpty
|
||||||
|
? ColorsManager.whiteColorsWithOpacity
|
||||||
|
: ColorsManager.whiteColors,
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,98 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
import '../../../../../utils/color_manager.dart';
|
||||||
|
import '../../../all_spaces/model/subspace_model.dart';
|
||||||
|
import '../../../create_subspace_model/widgets/subspace_chip.dart';
|
||||||
|
import '../../../space_model/models/subspace_template_model.dart';
|
||||||
|
import '../../bloc/subspace_bloc.dart';
|
||||||
|
import '../../bloc/subspace_event.dart';
|
||||||
|
|
||||||
|
class TextFieldSubSpaceDialogWidget extends StatelessWidget {
|
||||||
|
const TextFieldSubSpaceDialogWidget({
|
||||||
|
super.key,
|
||||||
|
required TextEditingController subspaceNameController,
|
||||||
|
required this.subSpaces,
|
||||||
|
}) : _subspaceNameController = subspaceNameController;
|
||||||
|
|
||||||
|
final TextEditingController _subspaceNameController;
|
||||||
|
final List<SubspaceModel> subSpaces;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: context.screenWidth * 0.35,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 10,
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.boxColor,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 8,
|
||||||
|
alignment: WrapAlignment.start,
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
|
children: [
|
||||||
|
...subSpaces.asMap().entries.map(
|
||||||
|
(entry) {
|
||||||
|
final index = entry.key;
|
||||||
|
final subSpace = entry.value;
|
||||||
|
|
||||||
|
final lowerName = subSpace.subspaceName.toLowerCase();
|
||||||
|
|
||||||
|
final duplicateIndices = subSpaces
|
||||||
|
.asMap()
|
||||||
|
.entries
|
||||||
|
.where((e) => e.value.subspaceName.toLowerCase() == lowerName)
|
||||||
|
.map((e) => e.key)
|
||||||
|
.toList();
|
||||||
|
final isDuplicate = duplicateIndices.length > 1 &&
|
||||||
|
duplicateIndices.indexOf(index) != 0;
|
||||||
|
return SubspaceChip(
|
||||||
|
subSpace: SubspaceTemplateModel(
|
||||||
|
subspaceName: entry.value.subspaceName,
|
||||||
|
disabled: entry.value.disabled,
|
||||||
|
),
|
||||||
|
isDuplicate: isDuplicate,
|
||||||
|
onDeleted: () => context.read<SubSpaceBloc>().add(
|
||||||
|
RemoveSubSpace(subSpace),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 200,
|
||||||
|
child: TextField(
|
||||||
|
controller: _subspaceNameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
hintText: subSpaces.isEmpty ? 'Please enter the name' : null,
|
||||||
|
hintStyle: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.lightGrayColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onSubmitted: (value) {
|
||||||
|
final trimmedValue = value.trim();
|
||||||
|
if (trimmedValue.isNotEmpty) {
|
||||||
|
context.read<SubSpaceBloc>().add(
|
||||||
|
AddSubSpace(
|
||||||
|
SubspaceModel(
|
||||||
|
subspaceName: trimmedValue,
|
||||||
|
disabled: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_subspaceNameController.clear();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: context.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -14,11 +14,11 @@ class CenterBodyWidget extends StatelessWidget {
|
|||||||
if (state is InitialState) {
|
if (state is InitialState) {
|
||||||
context.read<CenterBodyBloc>().add(CommunityStructureSelectedEvent());
|
context.read<CenterBodyBloc>().add(CommunityStructureSelectedEvent());
|
||||||
}
|
}
|
||||||
if (state is CommunityStructureState) {
|
if (state is CommunityStructureState) {
|
||||||
context.read<SpaceManagementBloc>().add(BlankStateEvent(context));
|
context.read<SpaceManagementBloc>().add(BlankStateEvent(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state is SpaceModelState) {
|
if (state is SpaceModelState) {
|
||||||
context.read<SpaceManagementBloc>().add(SpaceModelLoadEvent(context));
|
context.read<SpaceManagementBloc>().add(SpaceModelLoadEvent(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,15 +31,19 @@ class CenterBodyWidget extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.read<CenterBodyBloc>().add(CommunityStructureSelectedEvent());
|
context
|
||||||
|
.read<CenterBodyBloc>()
|
||||||
|
.add(CommunityStructureSelectedEvent());
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'Community Structure',
|
'Community Structure',
|
||||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||||
fontWeight: state is CommunityStructureState || state is CommunitySelectedState
|
fontWeight: state is CommunityStructureState ||
|
||||||
|
state is CommunitySelectedState
|
||||||
? FontWeight.bold
|
? FontWeight.bold
|
||||||
: FontWeight.normal,
|
: FontWeight.normal,
|
||||||
color: state is CommunityStructureState || state is CommunitySelectedState
|
color: state is CommunityStructureState ||
|
||||||
|
state is CommunitySelectedState
|
||||||
? Theme.of(context).textTheme.bodyLarge!.color
|
? Theme.of(context).textTheme.bodyLarge!.color
|
||||||
: Theme.of(context)
|
: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
@ -50,26 +54,26 @@ class CenterBodyWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 20),
|
const SizedBox(width: 20),
|
||||||
GestureDetector(
|
// GestureDetector(
|
||||||
onTap: () {
|
// onTap: () {
|
||||||
context.read<CenterBodyBloc>().add(SpaceModelSelectedEvent());
|
// context.read<CenterBodyBloc>().add(SpaceModelSelectedEvent());
|
||||||
},
|
// },
|
||||||
child: Text(
|
// child: Text(
|
||||||
'Space Model',
|
// 'Space Model',
|
||||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
// style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||||
fontWeight: state is SpaceModelState
|
// fontWeight: state is SpaceModelState
|
||||||
? FontWeight.bold
|
// ? FontWeight.bold
|
||||||
: FontWeight.normal,
|
// : FontWeight.normal,
|
||||||
color: state is SpaceModelState
|
// color: state is SpaceModelState
|
||||||
? Theme.of(context).textTheme.bodyLarge!.color
|
// ? Theme.of(context).textTheme.bodyLarge!.color
|
||||||
: Theme.of(context)
|
// : Theme.of(context)
|
||||||
.textTheme
|
// .textTheme
|
||||||
.bodyLarge!
|
// .bodyLarge!
|
||||||
.color!
|
// .color!
|
||||||
.withOpacity(0.5),
|
// .withOpacity(0.5),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -24,37 +24,22 @@ class AddDeviceTypeModelBloc
|
|||||||
|
|
||||||
if (currentState is AddDeviceModelLoaded) {
|
if (currentState is AddDeviceModelLoaded) {
|
||||||
final existingProduct = currentState.selectedProducts.firstWhere(
|
final existingProduct = currentState.selectedProducts.firstWhere(
|
||||||
(p) => p.productId == event.productId,
|
(p) => p.productId == event.selectedProduct.productId,
|
||||||
orElse: () => SelectedProduct(
|
orElse: () => event.selectedProduct,
|
||||||
productId: event.productId,
|
|
||||||
count: 0,
|
|
||||||
productName: event.productName,
|
|
||||||
product: event.product,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
List<SelectedProduct> updatedProducts;
|
List<SelectedProduct> updatedProducts;
|
||||||
|
|
||||||
if (event.count > 0) {
|
if (event.selectedProduct.count > 0) {
|
||||||
if (!currentState.selectedProducts.contains(existingProduct)) {
|
if (!currentState.selectedProducts.contains(existingProduct)) {
|
||||||
updatedProducts = [
|
updatedProducts = [
|
||||||
...currentState.selectedProducts,
|
...currentState.selectedProducts,
|
||||||
SelectedProduct(
|
event.selectedProduct,
|
||||||
productId: event.productId,
|
|
||||||
count: event.count,
|
|
||||||
productName: event.productName,
|
|
||||||
product: event.product,
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
updatedProducts = currentState.selectedProducts.map((p) {
|
updatedProducts = currentState.selectedProducts.map((p) {
|
||||||
if (p.productId == event.productId) {
|
if (p.productId == event.selectedProduct.productId) {
|
||||||
return SelectedProduct(
|
return event.selectedProduct;
|
||||||
productId: p.productId,
|
|
||||||
count: event.count,
|
|
||||||
productName: p.productName,
|
|
||||||
product: p.product,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
}).toList();
|
}).toList();
|
||||||
@ -62,7 +47,7 @@ class AddDeviceTypeModelBloc
|
|||||||
} else {
|
} else {
|
||||||
// Remove the product if the count is 0
|
// Remove the product if the count is 0
|
||||||
updatedProducts = currentState.selectedProducts
|
updatedProducts = currentState.selectedProducts
|
||||||
.where((p) => p.productId != event.productId)
|
.where((p) => p.productId != event.selectedProduct.productId)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,20 +10,15 @@ abstract class AddDeviceTypeModelEvent extends Equatable {
|
|||||||
List<Object> get props => [];
|
List<Object> get props => [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class UpdateProductCountEvent extends AddDeviceTypeModelEvent {
|
class UpdateProductCountEvent extends AddDeviceTypeModelEvent {
|
||||||
final String productId;
|
final SelectedProduct selectedProduct;
|
||||||
final int count;
|
|
||||||
final String productName;
|
|
||||||
final ProductModel product;
|
|
||||||
|
|
||||||
UpdateProductCountEvent({required this.productId, required this.count, required this.productName, required this.product});
|
UpdateProductCountEvent({required this.selectedProduct});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [productId, count];
|
List<Object> get props => [selectedProduct];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class InitializeDeviceTypeModel extends AddDeviceTypeModelEvent {
|
class InitializeDeviceTypeModel extends AddDeviceTypeModelEvent {
|
||||||
final List<Tag> initialTags;
|
final List<Tag> initialTags;
|
||||||
final List<SelectedProduct> addedProducts;
|
final List<SelectedProduct> addedProducts;
|
||||||
|
|||||||
@ -54,10 +54,10 @@ class DeviceTypeTileWidget extends StatelessWidget {
|
|||||||
onCountChanged: (newCount) {
|
onCountChanged: (newCount) {
|
||||||
context.read<AddDeviceTypeModelBloc>().add(
|
context.read<AddDeviceTypeModelBloc>().add(
|
||||||
UpdateProductCountEvent(
|
UpdateProductCountEvent(
|
||||||
productId: product.uuid,
|
selectedProduct: product.toSelectedProduct(
|
||||||
count: newCount,
|
newCount,
|
||||||
productName: product.catName,
|
),
|
||||||
product: product),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@ -22,6 +22,18 @@ class HTTPService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
client.interceptors.add(serviceLocator.get<HTTPInterceptor>());
|
client.interceptors.add(serviceLocator.get<HTTPInterceptor>());
|
||||||
|
// Add this interceptor for logging requests and responses
|
||||||
|
// client.interceptors.add(
|
||||||
|
// LogInterceptor(
|
||||||
|
// request: true,
|
||||||
|
// requestHeader: true,
|
||||||
|
// requestBody: true,
|
||||||
|
// responseHeader: false,
|
||||||
|
// responseBody: true,
|
||||||
|
// error: true,
|
||||||
|
// logPrint: (object) => print(object),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,22 +4,27 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_m
|
|||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.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';
|
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||||
|
|
||||||
|
import '../pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||||
|
|
||||||
class CommunitySpaceManagementApi {
|
class CommunitySpaceManagementApi {
|
||||||
// Community Management APIs
|
// Community Management APIs
|
||||||
Future<List<CommunityModel>> fetchCommunities(String projectId, {int page = 1}) async {
|
Future<List<CommunityModel>> fetchCommunities(String projectId,
|
||||||
|
{int page = 1}) async {
|
||||||
try {
|
try {
|
||||||
List<CommunityModel> allCommunities = [];
|
List<CommunityModel> allCommunities = [];
|
||||||
bool hasNext = true;
|
bool hasNext = true;
|
||||||
|
|
||||||
while (hasNext) {
|
while (hasNext) {
|
||||||
await HTTPService().get(
|
await HTTPService().get(
|
||||||
path: ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId),
|
path: ApiEndpoints.getCommunityList
|
||||||
|
.replaceAll('{projectId}', projectId),
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
'page': page,
|
'page': page,
|
||||||
},
|
},
|
||||||
@ -55,8 +60,14 @@ class CommunitySpaceManagementApi {
|
|||||||
try {
|
try {
|
||||||
bool hasNext = false;
|
bool hasNext = false;
|
||||||
await HTTPService().get(
|
await HTTPService().get(
|
||||||
path: ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId),
|
path:
|
||||||
queryParameters: {'page': page, 'includeSpaces': true, 'size': 25, 'search': search},
|
ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectId),
|
||||||
|
queryParameters: {
|
||||||
|
'page': page,
|
||||||
|
'includeSpaces': true,
|
||||||
|
'size': 25,
|
||||||
|
'search': search
|
||||||
|
},
|
||||||
expectedResponseModel: (json) {
|
expectedResponseModel: (json) {
|
||||||
try {
|
try {
|
||||||
List<dynamic> jsonData = json['data'] ?? [];
|
List<dynamic> jsonData = json['data'] ?? [];
|
||||||
@ -68,7 +79,10 @@ class CommunitySpaceManagementApi {
|
|||||||
|
|
||||||
page = currentPage + 1;
|
page = currentPage + 1;
|
||||||
paginationModel = PaginationModel(
|
paginationModel = PaginationModel(
|
||||||
pageNum: page, hasNext: hasNext, size: 25, communities: communityList);
|
pageNum: page,
|
||||||
|
hasNext: hasNext,
|
||||||
|
size: 25,
|
||||||
|
communities: communityList);
|
||||||
return paginationModel;
|
return paginationModel;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
hasNext = false;
|
hasNext = false;
|
||||||
@ -83,7 +97,8 @@ class CommunitySpaceManagementApi {
|
|||||||
Future<CommunityModel?> getCommunityById(String communityId) async {
|
Future<CommunityModel?> getCommunityById(String communityId) async {
|
||||||
try {
|
try {
|
||||||
final response = await HTTPService().get(
|
final response = await HTTPService().get(
|
||||||
path: ApiEndpoints.getCommunityById.replaceAll('{communityId}', communityId),
|
path: ApiEndpoints.getCommunityById
|
||||||
|
.replaceAll('{communityId}', communityId),
|
||||||
expectedResponseModel: (json) {
|
expectedResponseModel: (json) {
|
||||||
return CommunityModel.fromJson(json['data']);
|
return CommunityModel.fromJson(json['data']);
|
||||||
},
|
},
|
||||||
@ -95,7 +110,8 @@ class CommunitySpaceManagementApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<CommunityModel?> createCommunity(String name, String description, String projectId) async {
|
Future<CommunityModel?> createCommunity(
|
||||||
|
String name, String description, String projectId) async {
|
||||||
try {
|
try {
|
||||||
final response = await HTTPService().post(
|
final response = await HTTPService().post(
|
||||||
path: ApiEndpoints.createCommunity.replaceAll('{projectId}', projectId),
|
path: ApiEndpoints.createCommunity.replaceAll('{projectId}', projectId),
|
||||||
@ -114,7 +130,8 @@ class CommunitySpaceManagementApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> updateCommunity(String communityId, String name, String projectId) async {
|
Future<bool> updateCommunity(
|
||||||
|
String communityId, String name, String projectId) async {
|
||||||
try {
|
try {
|
||||||
final response = await HTTPService().put(
|
final response = await HTTPService().put(
|
||||||
path: ApiEndpoints.updateCommunity
|
path: ApiEndpoints.updateCommunity
|
||||||
@ -151,7 +168,8 @@ class CommunitySpaceManagementApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SpacesResponse> fetchSpaces(String communityId, String projectId) async {
|
Future<SpacesResponse> fetchSpaces(
|
||||||
|
String communityId, String projectId) async {
|
||||||
try {
|
try {
|
||||||
final response = await HTTPService().get(
|
final response = await HTTPService().get(
|
||||||
path: ApiEndpoints.listSpaces
|
path: ApiEndpoints.listSpaces
|
||||||
@ -177,7 +195,8 @@ class CommunitySpaceManagementApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SpaceModel?> getSpace(String communityId, String spaceId, String projectId) async {
|
Future<SpaceModel?> getSpace(
|
||||||
|
String communityId, String spaceId, String projectId) async {
|
||||||
try {
|
try {
|
||||||
final response = await HTTPService().get(
|
final response = await HTTPService().get(
|
||||||
path: ApiEndpoints.getSpace
|
path: ApiEndpoints.getSpace
|
||||||
@ -199,7 +218,6 @@ class CommunitySpaceManagementApi {
|
|||||||
{required String communityId,
|
{required String communityId,
|
||||||
required String name,
|
required String name,
|
||||||
String? parentId,
|
String? parentId,
|
||||||
String? direction,
|
|
||||||
bool isPrivate = false,
|
bool isPrivate = false,
|
||||||
required Offset position,
|
required Offset position,
|
||||||
String? spaceModelUuid,
|
String? spaceModelUuid,
|
||||||
@ -213,7 +231,6 @@ class CommunitySpaceManagementApi {
|
|||||||
'isPrivate': isPrivate,
|
'isPrivate': isPrivate,
|
||||||
'x': position.dx,
|
'x': position.dx,
|
||||||
'y': position.dy,
|
'y': position.dy,
|
||||||
'direction': direction,
|
|
||||||
'icon': icon,
|
'icon': icon,
|
||||||
};
|
};
|
||||||
if (parentId != null) {
|
if (parentId != null) {
|
||||||
@ -248,11 +265,10 @@ class CommunitySpaceManagementApi {
|
|||||||
required String name,
|
required String name,
|
||||||
String? parentId,
|
String? parentId,
|
||||||
String? icon,
|
String? icon,
|
||||||
String? direction,
|
|
||||||
bool isPrivate = false,
|
bool isPrivate = false,
|
||||||
required Offset position,
|
required Offset position,
|
||||||
List<TagModelUpdate>? tags,
|
List<Tag>? tags,
|
||||||
List<UpdateSubspaceTemplateModel>? subspaces,
|
List<SubspaceModel>? subspaces,
|
||||||
String? spaceModelUuid,
|
String? spaceModelUuid,
|
||||||
required String projectId}) async {
|
required String projectId}) async {
|
||||||
try {
|
try {
|
||||||
@ -261,9 +277,8 @@ class CommunitySpaceManagementApi {
|
|||||||
'isPrivate': isPrivate,
|
'isPrivate': isPrivate,
|
||||||
'x': position.dx,
|
'x': position.dx,
|
||||||
'y': position.dy,
|
'y': position.dy,
|
||||||
'direction': direction,
|
|
||||||
'icon': icon,
|
'icon': icon,
|
||||||
'subspace': subspaces,
|
'subspaces': subspaces,
|
||||||
'tags': tags,
|
'tags': tags,
|
||||||
'spaceModelUuid': spaceModelUuid,
|
'spaceModelUuid': spaceModelUuid,
|
||||||
};
|
};
|
||||||
@ -289,7 +304,8 @@ class CommunitySpaceManagementApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> deleteSpace(String communityId, String spaceId, String projectId) async {
|
Future<bool> deleteSpace(
|
||||||
|
String communityId, String spaceId, String projectId) async {
|
||||||
try {
|
try {
|
||||||
final response = await HTTPService().delete(
|
final response = await HTTPService().delete(
|
||||||
path: ApiEndpoints.deleteSpace
|
path: ApiEndpoints.deleteSpace
|
||||||
@ -307,15 +323,17 @@ class CommunitySpaceManagementApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<SpaceModel>> getSpaceHierarchy(String communityId, String projectId) async {
|
Future<List<SpaceModel>> getSpaceHierarchy(
|
||||||
|
String communityId, String projectId) async {
|
||||||
try {
|
try {
|
||||||
final response = await HTTPService().get(
|
final response = await HTTPService().get(
|
||||||
path: ApiEndpoints.getSpaceHierarchy
|
path: ApiEndpoints.getSpaceHierarchy
|
||||||
.replaceAll('{communityId}', communityId)
|
.replaceAll('{communityId}', communityId)
|
||||||
.replaceAll('{projectId}', projectId),
|
.replaceAll('{projectId}', projectId),
|
||||||
expectedResponseModel: (json) {
|
expectedResponseModel: (json) {
|
||||||
final spaceModels =
|
final spaceModels = (json['data'] as List)
|
||||||
(json['data'] as List).map((spaceJson) => SpaceModel.fromJson(spaceJson)).toList();
|
.map((spaceJson) => SpaceModel.fromJson(spaceJson))
|
||||||
|
.toList();
|
||||||
|
|
||||||
return spaceModels;
|
return spaceModels;
|
||||||
},
|
},
|
||||||
@ -327,15 +345,17 @@ class CommunitySpaceManagementApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<SpaceModel>> getSpaceOnlyWithDevices({String? communityId, String? projectId}) async {
|
Future<List<SpaceModel>> getSpaceOnlyWithDevices(
|
||||||
|
{String? communityId, String? projectId}) async {
|
||||||
try {
|
try {
|
||||||
final response = await HTTPService().get(
|
final response = await HTTPService().get(
|
||||||
path: ApiEndpoints.spaceOnlyWithDevices
|
path: ApiEndpoints.spaceOnlyWithDevices
|
||||||
.replaceAll('{communityId}', communityId!)
|
.replaceAll('{communityId}', communityId!)
|
||||||
.replaceAll('{projectId}', projectId!),
|
.replaceAll('{projectId}', projectId!),
|
||||||
expectedResponseModel: (json) {
|
expectedResponseModel: (json) {
|
||||||
final spaceModels =
|
final spaceModels = (json['data'] as List)
|
||||||
(json['data'] as List).map((spaceJson) => SpaceModel.fromJson(spaceJson)).toList();
|
.map((spaceJson) => SpaceModel.fromJson(spaceJson))
|
||||||
|
.toList();
|
||||||
return spaceModels;
|
return spaceModels;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/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/space_template_model.dart';
|
||||||
@ -7,17 +9,23 @@ import 'package:syncrow_web/utils/constants/api_const.dart';
|
|||||||
class SpaceModelManagementApi {
|
class SpaceModelManagementApi {
|
||||||
Future<List<SpaceTemplateModel>> listSpaceModels(
|
Future<List<SpaceTemplateModel>> listSpaceModels(
|
||||||
{required String projectId, int page = 1}) async {
|
{required String projectId, int page = 1}) async {
|
||||||
final response = await HTTPService().get(
|
try {
|
||||||
path: ApiEndpoints.listSpaceModels.replaceAll('{projectId}', projectId),
|
// final response = await HTTPService().get(
|
||||||
queryParameters: {'page': page},
|
// path: ApiEndpoints.listSpaceModels.replaceAll('{projectId}', projectId),
|
||||||
expectedResponseModel: (json) {
|
// queryParameters: {'page': page},
|
||||||
List<dynamic> jsonData = json['data'];
|
// expectedResponseModel: (json) {
|
||||||
return jsonData.map((jsonItem) {
|
// List<dynamic> jsonData = json['data'];
|
||||||
return SpaceTemplateModel.fromJson(jsonItem);
|
// return jsonData.map((jsonItem) {
|
||||||
}).toList();
|
// return SpaceTemplateModel.fromJson(jsonItem);
|
||||||
},
|
// }).toList();
|
||||||
);
|
// },
|
||||||
return response;
|
// );
|
||||||
|
return [];
|
||||||
|
// response;
|
||||||
|
} catch (e) {
|
||||||
|
log(e.toString());
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SpaceTemplateModel?> createSpaceModel(
|
Future<SpaceTemplateModel?> createSpaceModel(
|
||||||
@ -33,8 +41,8 @@ class SpaceModelManagementApi {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> updateSpaceModel(
|
Future<String?> updateSpaceModel(CreateSpaceTemplateBodyModel spaceModel,
|
||||||
CreateSpaceTemplateBodyModel spaceModel, String spaceModelUuid, String projectId) async {
|
String spaceModelUuid, String projectId) async {
|
||||||
final response = await HTTPService().put(
|
final response = await HTTPService().put(
|
||||||
path: ApiEndpoints.updateSpaceModel
|
path: ApiEndpoints.updateSpaceModel
|
||||||
.replaceAll('{projectId}', projectId)
|
.replaceAll('{projectId}', projectId)
|
||||||
@ -47,7 +55,8 @@ class SpaceModelManagementApi {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SpaceTemplateModel?> getSpaceModel(String spaceModelUuid, String projectId) async {
|
Future<SpaceTemplateModel?> getSpaceModel(
|
||||||
|
String spaceModelUuid, String projectId) async {
|
||||||
final response = await HTTPService().get(
|
final response = await HTTPService().get(
|
||||||
path: ApiEndpoints.getSpaceModel
|
path: ApiEndpoints.getSpaceModel
|
||||||
.replaceAll('{projectId}', projectId)
|
.replaceAll('{projectId}', projectId)
|
||||||
|
|||||||
@ -2,6 +2,7 @@ class Assets {
|
|||||||
Assets._();
|
Assets._();
|
||||||
static const String background = "assets/images/Background.png";
|
static const String background = "assets/images/Background.png";
|
||||||
static const String webBackground = "assets/images/web_Background.svg";
|
static const String webBackground = "assets/images/web_Background.svg";
|
||||||
|
static const String webBackgroundPng = "assets/images/web_Background.png";
|
||||||
static const String blackLogo = "assets/images/black-logo.png";
|
static const String blackLogo = "assets/images/black-logo.png";
|
||||||
static const String logo = "assets/images/Logo.svg";
|
static const String logo = "assets/images/Logo.svg";
|
||||||
static const String logoHorizontal = "assets/images/logo_horizontal.png";
|
static const String logoHorizontal = "assets/images/logo_horizontal.png";
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user