mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 15:17:31 +00:00
Compare commits
32 Commits
feat/space
...
bugfix/edi
Author | SHA1 | Date | |
---|---|---|---|
9091af2661 | |||
4b7f4d4279 | |||
e61cfd7e49 | |||
788fb75a68 | |||
c72297e0c8 | |||
d0c6b13072 | |||
812dc4792b | |||
4907eebc42 | |||
9167c8da29 | |||
4258ccdfbd | |||
cb71b51565 | |||
d4ed4efcd8 | |||
dac045146e | |||
5563197e9d | |||
7268253e35 | |||
24f7ab6af8 | |||
65d00c923a | |||
830725254f | |||
ba7db3a5fb | |||
f35b699d4c | |||
7ffdc67016 | |||
18afc4f563 | |||
44d95f5701 | |||
e47f3d6d59 | |||
788ea27de1 | |||
81e9e58627 | |||
25eae3dfaa | |||
59eafc99a5 | |||
db7eaa53af | |||
20a9f19480 | |||
eb7eeebf18 | |||
15023e5882 |
16
assets/icons/duplicate.svg
Normal file
16
assets/icons/duplicate.svg
Normal file
@ -0,0 +1,16 @@
|
||||
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.9807 2.67199L16.6413 0.219727H7.91051C7.43122 0.219727 7.04266 0.608281 7.04266 1.08758V16.605C7.04266 17.0843 7.43122 17.4729 7.91051 17.4729H18.2257C18.705 17.4729 19.0935 17.0843 19.0935 16.605V16.5459H19.7312V3.8127L17.9807 2.67199Z" fill="#60B7FF"/>
|
||||
<path d="M18.1513 6.23445H9.12553C8.95881 6.23445 8.82373 6.09934 8.82373 5.93266C8.82373 5.76598 8.95885 5.63086 9.12553 5.63086H18.1513C18.318 5.63086 18.4531 5.76598 18.4531 5.93266C18.4531 6.09934 18.318 6.23445 18.1513 6.23445Z" fill="#0055A3"/>
|
||||
<path d="M18.1513 8.54891H9.12553C8.95881 8.54891 8.82373 8.41379 8.82373 8.24711C8.82373 8.08043 8.95885 7.94531 9.12553 7.94531H18.1513C18.318 7.94531 18.4531 8.08043 18.4531 8.24711C18.4531 8.41379 18.318 8.54891 18.1513 8.54891Z" fill="#0055A3"/>
|
||||
<path d="M18.1513 10.8634H9.12553C8.95881 10.8634 8.82373 10.7282 8.82373 10.5616C8.82373 10.3949 8.95885 10.2598 9.12553 10.2598H18.1513C18.318 10.2598 18.4531 10.3949 18.4531 10.5616C18.4531 10.7282 18.318 10.8634 18.1513 10.8634Z" fill="#0055A3"/>
|
||||
<path d="M18.1513 13.1778H9.12556C8.95884 13.1778 8.82376 13.0427 8.82376 12.876C8.82376 12.7093 8.95888 12.5742 9.12556 12.5742H18.1513C18.3181 12.5742 18.4531 12.7093 18.4531 12.876C18.4531 13.0427 18.3181 13.1778 18.1513 13.1778Z" fill="#0055A3"/>
|
||||
<path d="M19.0935 3.39648V16.6044C19.0935 17.0837 18.7049 17.4722 18.2256 17.4722H19.3663C19.8456 17.4722 20.2342 17.0837 20.2342 16.6044V3.81203L19.0935 3.39648Z" fill="#26A6FE"/>
|
||||
<path d="M17.5091 3.8127H20.2342L16.6413 0.219727V2.94484C16.6413 3.42414 17.0298 3.8127 17.5091 3.8127Z" fill="#004281"/>
|
||||
<path d="M11.4172 19.7805C11.8965 19.7805 12.2851 19.392 12.2851 18.9127V18.8937H12.8297V6.12031L11.039 4.78906L9.83279 2.52734H1.10204C0.622747 2.52734 0.234192 2.9159 0.234192 3.3952V18.9127C0.234192 19.392 0.622747 19.7805 1.10204 19.7805H11.4172Z" fill="#D5EDFF"/>
|
||||
<path d="M12.285 4.97852V6.11922V18.9116C12.285 19.3909 11.8964 19.7794 11.4171 19.7794H12.5578C13.0371 19.7794 13.4257 19.3909 13.4257 18.9116V6.11922L12.285 4.97852Z" fill="#D8ECFE"/>
|
||||
<path d="M10.7006 6.12031H13.4258L9.83279 2.52734V5.25246C9.83276 5.73176 10.2213 6.12031 10.7006 6.12031Z" fill="#B3DAFE"/>
|
||||
<path d="M11.3429 8.54891H2.31709C2.15037 8.54891 2.01529 8.41379 2.01529 8.24711C2.01529 8.08043 2.15041 7.94531 2.31709 7.94531H11.3429C11.5096 7.94531 11.6447 8.08043 11.6447 8.24711C11.6447 8.41379 11.5096 8.54891 11.3429 8.54891Z" fill="#82AEE3"/>
|
||||
<path d="M11.3428 10.8634H2.31706C2.15034 10.8634 2.01526 10.7282 2.01526 10.5616C2.01526 10.3949 2.15038 10.2598 2.31706 10.2598H11.3428C11.5096 10.2598 11.6446 10.3949 11.6446 10.5616C11.6446 10.7282 11.5096 10.8634 11.3428 10.8634Z" fill="#82AEE3"/>
|
||||
<path d="M11.3428 13.1778H2.31706C2.15034 13.1778 2.01526 13.0427 2.01526 12.876C2.01526 12.7093 2.15038 12.5742 2.31706 12.5742H11.3428C11.5096 12.5742 11.6446 12.7093 11.6446 12.876C11.6446 13.0427 11.5096 13.1778 11.3428 13.1778Z" fill="#82AEE3"/>
|
||||
<path d="M11.3428 15.4923H2.31706C2.15034 15.4923 2.01526 15.3571 2.01526 15.1905C2.01526 15.0238 2.15038 14.8887 2.31706 14.8887H11.3428C11.5096 14.8887 11.6446 15.0238 11.6446 15.1905C11.6447 15.3571 11.5096 15.4923 11.3428 15.4923Z" fill="#82AEE3"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
22
assets/icons/edit_space.svg
Normal file
22
assets/icons/edit_space.svg
Normal file
@ -0,0 +1,22 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_4618_3290)">
|
||||
<path d="M18.7967 7.35156V19.8515C18.7967 21.0362 17.8329 22 16.6482 22H4.14825C2.9636 22 1.99982 21.0362 1.99982 19.8515V7.35156C1.99982 6.16691 2.9636 5.20312 4.14825 5.20312H16.6482C17.8329 5.20312 18.7967 6.16691 18.7967 7.35156Z" fill="#EDF2F2"/>
|
||||
<path d="M18.7967 19.8515C18.7967 21.0361 17.8329 21.9999 16.6482 21.9999H4.14825C3.55591 21.9999 3.0188 21.7589 2.62978 21.3699L18.1667 5.83301C18.5557 6.22203 18.7967 6.75914 18.7967 7.35148V19.8515Z" fill="#C9DCDC"/>
|
||||
<path d="M9.28417 14.7153C9.12722 14.5583 9.07241 14.3262 9.14265 14.1157L9.97128 11.6297C10 11.5434 10.0485 11.465 10.1128 11.4007L17.8468 3.66674C18.0756 3.43791 18.4466 3.43791 18.6754 3.66674L20.3327 5.324C20.5615 5.55283 20.5615 5.9238 20.3327 6.15263L12.5987 13.8866C12.5344 13.9509 12.456 13.9994 12.3697 14.0281L9.88374 14.8567C9.67323 14.927 9.44109 14.8722 9.28417 14.7153Z" fill="#4998EE"/>
|
||||
<path d="M19.504 4.49512L9.28413 14.715C9.44105 14.8719 9.6732 14.9267 9.88374 14.8565L12.3697 14.0279C12.456 13.9992 12.5344 13.9507 12.5987 13.8864L20.3327 6.15242C20.5615 5.92359 20.5615 5.55261 20.3327 5.32379L19.504 4.49512Z" fill="#176EDE"/>
|
||||
<path d="M20.3327 6.15305L17.8467 3.66711L19.2278 2.28602C19.6092 1.90466 20.2275 1.90466 20.6089 2.28602L21.7137 3.39087C22.0951 3.77223 22.0951 4.39055 21.7137 4.77192L20.3327 6.15305Z" fill="#FFE137"/>
|
||||
<path d="M21.1613 2.83789L19.0897 4.90949L20.3327 6.15245L21.7138 4.77136C22.0951 4.39 22.0951 3.77168 21.7138 3.39031L21.1613 2.83789Z" fill="#FAC814"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_4618_3290" x="-0.000183105" y="0" width="24" height="24" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="1"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4618_3290"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_4618_3290" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
9
assets/icons/space_delete.svg
Normal file
9
assets/icons/space_delete.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<svg width="16" height="20" viewBox="0 0 16 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.8069 19.9998H3.50035C2.16411 19.9998 1.08087 18.9165 1.08087 17.5804V4.51562H14.2262V17.5804C14.2262 18.9165 13.1432 19.9998 11.8069 19.9998Z" fill="#D8D8D8"/>
|
||||
<path d="M1.08087 4.51562V5.48335H12.3715C12.8168 5.48335 13.1779 5.84438 13.1779 6.2898V17.3384C13.1779 18.2292 12.4557 18.9513 11.5649 18.9513H2.4519C2.05409 18.9513 1.67887 18.8547 1.34775 18.6844C1.74907 19.4652 2.56207 19.9998 3.50035 19.9998H11.8069C13.1432 19.9998 14.2262 18.9165 14.2262 17.5803V4.51562H1.08087Z" fill="#BABABA"/>
|
||||
<path d="M14.2667 1.77417H10.7439L10.1633 0.612956C9.9742 0.234837 9.5941 0 9.17142 0H6.13594C5.71311 0 5.33316 0.234837 5.1441 0.612956L4.56349 1.77417H1.04063C0.595221 1.77417 0.234192 2.1352 0.234192 2.58061V3.70978C0.234192 4.15504 0.595221 4.51622 1.04063 4.51622H14.2667C14.7121 4.51622 15.0732 4.15504 15.0732 3.70978V2.58077C15.0732 2.1352 14.7121 1.77417 14.2667 1.77417ZM5.68503 0.8835C5.77094 0.71153 5.94368 0.604869 6.13594 0.604869H9.17142C9.36354 0.604869 9.53627 0.71153 9.62218 0.8835L10.0676 1.77417H5.23977L5.68503 0.8835Z" fill="#757575"/>
|
||||
<path d="M14.2668 1.77441H12.9763C13.4217 1.77441 13.7829 2.13544 13.7829 2.58086V3.71003C13.7829 4.15529 13.4217 4.51647 12.9763 4.51647H14.2668C14.7122 4.51647 15.0732 4.15529 15.0732 3.71003V2.58101C15.0732 2.13544 14.7122 1.77441 14.2668 1.77441Z" fill="#595959"/>
|
||||
<path d="M11.3634 17.5C10.918 17.5 10.5569 17.139 10.5569 16.6935V9.15312C10.5569 8.70771 10.918 8.34668 11.3634 8.34668C11.8088 8.34668 12.1698 8.70771 12.1698 9.15312V16.6935C12.1698 17.139 11.8088 17.5 11.3634 17.5Z" fill="#757575"/>
|
||||
<path d="M3.94398 17.5C4.38924 17.5 4.75043 17.139 4.75043 16.6935V9.15312C4.75043 8.70771 4.38924 8.34668 3.94398 8.34668C3.49857 8.34668 3.13739 8.70771 3.13739 9.15312V16.6935C3.13739 17.139 3.49857 17.5 3.94398 17.5Z" fill="#757575"/>
|
||||
<path d="M7.65361 17.5C7.2082 17.5 6.84717 17.139 6.84717 16.6935V9.15312C6.84717 8.70771 7.2082 8.34668 7.65361 8.34668C8.09902 8.34668 8.46005 8.70771 8.46005 9.15312V16.6935C8.46005 17.139 8.09902 17.5 7.65361 17.5Z" fill="#757575"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
39
lib/common/edit_chip.dart
Normal file
39
lib/common/edit_chip.dart
Normal file
@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class EditChip extends StatelessWidget {
|
||||
final String label;
|
||||
final VoidCallback onTap;
|
||||
final Color labelColor;
|
||||
final Color backgroundColor;
|
||||
final Color borderColor;
|
||||
final double borderRadius;
|
||||
|
||||
const EditChip({
|
||||
Key? key,
|
||||
this.label = 'Edit',
|
||||
required this.onTap,
|
||||
this.labelColor = ColorsManager.spaceColor,
|
||||
this.backgroundColor = ColorsManager.whiteColors,
|
||||
this.borderColor = ColorsManager.spaceColor,
|
||||
this.borderRadius = 16.0,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Chip(
|
||||
label: Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(color: labelColor)
|
||||
),
|
||||
backgroundColor: backgroundColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
side: BorderSide(color: borderColor),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -19,12 +19,14 @@ class DefaultButton extends StatelessWidget {
|
||||
this.padding,
|
||||
this.borderColor,
|
||||
this.elevation,
|
||||
this.borderWidth = 1.0,
|
||||
});
|
||||
final void Function()? onPressed;
|
||||
final Widget child;
|
||||
final double? height;
|
||||
final bool isSecondary;
|
||||
final double? borderRadius;
|
||||
final double borderWidth;
|
||||
final bool enabled;
|
||||
final double? padding;
|
||||
final bool isDone;
|
||||
@ -66,13 +68,16 @@ class DefaultButton extends StatelessWidget {
|
||||
}),
|
||||
shape: WidgetStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
side: BorderSide(color: borderColor ?? Colors.transparent),
|
||||
side: BorderSide(
|
||||
color: borderColor ?? Colors.transparent,
|
||||
width: borderWidth,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(borderRadius ?? 20),
|
||||
),
|
||||
),
|
||||
fixedSize: height != null
|
||||
? WidgetStateProperty.all(Size.fromHeight(height!))
|
||||
: null,
|
||||
? WidgetStateProperty.all(Size.fromHeight(height!))
|
||||
: null,
|
||||
padding: WidgetStateProperty.all(
|
||||
EdgeInsets.all(padding ?? 10),
|
||||
),
|
||||
|
@ -42,7 +42,7 @@ class RolesUserModel {
|
||||
invitedBy:
|
||||
json['invitedBy'].toString().toLowerCase().replaceAll("_", " "),
|
||||
phoneNumber: json['phoneNumber'],
|
||||
jobTitle: json['jobTitle'].toString(),
|
||||
jobTitle: json['jobTitle'] ?? "-",
|
||||
createdDate: json['createdDate'],
|
||||
createdTime: json['createdTime'],
|
||||
);
|
||||
|
@ -114,7 +114,7 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
|
||||
currentStep++;
|
||||
if (currentStep == 2) {
|
||||
_blocRole.add(
|
||||
CheckStepStatus(isEditUser: false));
|
||||
const CheckStepStatus(isEditUser: false));
|
||||
} else if (currentStep == 3) {
|
||||
_blocRole
|
||||
.add(const CheckSpacesStepStatus());
|
||||
@ -151,11 +151,11 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
|
||||
Widget _getFormContent() {
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
return BasicsView(
|
||||
return const BasicsView(
|
||||
userId: '',
|
||||
);
|
||||
case 2:
|
||||
return SpacesAccessView();
|
||||
return const SpacesAccessView();
|
||||
case 3:
|
||||
return const RolesAndPermission();
|
||||
default:
|
||||
@ -172,7 +172,7 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
|
||||
bloc.add(const CheckSpacesStepStatus());
|
||||
currentStep = step;
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
bloc.add(ValidateBasicsStep());
|
||||
bloc.add(const ValidateBasicsStep());
|
||||
});
|
||||
});
|
||||
|
||||
@ -237,10 +237,11 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
|
||||
onTap: () {
|
||||
setState(() {
|
||||
currentStep = step;
|
||||
bloc.add(CheckStepStatus(isEditUser: false));
|
||||
bloc.add(const CheckStepStatus(isEditUser: false));
|
||||
if (step3 == 3) {
|
||||
bloc.add(const CheckRoleStepStatus());
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
child: Column(
|
||||
|
@ -4,7 +4,6 @@ import 'package:intl_phone_field/countries.dart';
|
||||
import 'package:intl_phone_field/country_picker_dialog.dart';
|
||||
import 'package:intl_phone_field/intl_phone_field.dart';
|
||||
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
|
||||
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
|
||||
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
@ -47,7 +46,9 @@ class BasicsView extends StatelessWidget {
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.18,
|
||||
height: MediaQuery.of(context).size.width * 0.08,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -76,12 +77,12 @@ class BasicsView extends StatelessWidget {
|
||||
child: TextFormField(
|
||||
style:
|
||||
const TextStyle(color: ColorsManager.blackColor),
|
||||
onChanged: (value) {
|
||||
Future.delayed(const Duration(milliseconds: 200),
|
||||
() {
|
||||
_blocRole.add(ValidateBasicsStep());
|
||||
});
|
||||
},
|
||||
// onChanged: (value) {
|
||||
// Future.delayed(const Duration(milliseconds: 200),
|
||||
// () {
|
||||
// _blocRole.add(const ValidateBasicsStep());
|
||||
// });
|
||||
// },
|
||||
controller: _blocRole.firstNameController,
|
||||
decoration: inputTextFormDeco(
|
||||
hintText: "Enter first name",
|
||||
@ -103,7 +104,9 @@ class BasicsView extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.18,
|
||||
height: MediaQuery.of(context).size.width * 0.08,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -128,12 +131,12 @@ class BasicsView extends StatelessWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextFormField(
|
||||
onChanged: (value) {
|
||||
Future.delayed(const Duration(milliseconds: 200),
|
||||
() {
|
||||
_blocRole.add(ValidateBasicsStep());
|
||||
});
|
||||
},
|
||||
// onChanged: (value) {
|
||||
// Future.delayed(const Duration(milliseconds: 200),
|
||||
// () {
|
||||
// _blocRole.add(ValidateBasicsStep());
|
||||
// });
|
||||
// },
|
||||
controller: _blocRole.lastNameController,
|
||||
style: const TextStyle(color: Colors.black),
|
||||
decoration:
|
||||
@ -186,13 +189,13 @@ class BasicsView extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextFormField(
|
||||
enabled: userId != '' ? false : true,
|
||||
onChanged: (value) {
|
||||
Future.delayed(const Duration(milliseconds: 200), () {
|
||||
_blocRole.add(CheckStepStatus(
|
||||
isEditUser: userId != '' ? false : true));
|
||||
_blocRole.add(ValidateBasicsStep());
|
||||
});
|
||||
},
|
||||
// onChanged: (value) {
|
||||
// Future.delayed(const Duration(milliseconds: 200), () {
|
||||
// _blocRole.add(CheckStepStatus(
|
||||
// isEditUser: userId != '' ? false : true));
|
||||
// _blocRole.add(ValidateBasicsStep());
|
||||
// });
|
||||
// },
|
||||
controller: _blocRole.emailController,
|
||||
style: const TextStyle(color: ColorsManager.blackColor),
|
||||
decoration: inputTextFormDeco(hintText: "name@example.com")
|
||||
|
@ -11,7 +11,14 @@ class DeleteUserDialog extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DeleteUserDialogState extends State<DeleteUserDialog> {
|
||||
int currentStep = 1;
|
||||
bool isLoading = false;
|
||||
bool _isDisposed = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_isDisposed = true;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -56,7 +63,7 @@ class _DeleteUserDialogState extends State<DeleteUserDialog> {
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop(true);
|
||||
Navigator.of(context).pop(false); // Return false if canceled
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
@ -76,7 +83,26 @@ class _DeleteUserDialogState extends State<DeleteUserDialog> {
|
||||
)),
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: widget.onTapDelete,
|
||||
onTap: isLoading
|
||||
? null
|
||||
: () async {
|
||||
setState(() {
|
||||
isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
if (widget.onTapDelete != null) {
|
||||
await widget.onTapDelete!();
|
||||
}
|
||||
} finally {
|
||||
if (!_isDisposed) {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: const BoxDecoration(
|
||||
@ -91,13 +117,22 @@ class _DeleteUserDialogState extends State<DeleteUserDialog> {
|
||||
),
|
||||
),
|
||||
),
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'Delete',
|
||||
style: TextStyle(
|
||||
color: ColorsManager.red,
|
||||
),
|
||||
))),
|
||||
child: Center(
|
||||
child: isLoading
|
||||
? const SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
color: ColorsManager.red,
|
||||
strokeWidth: 2.0,
|
||||
),
|
||||
)
|
||||
: const Text(
|
||||
'Delete',
|
||||
style: TextStyle(
|
||||
color: ColorsManager.red,
|
||||
),
|
||||
))),
|
||||
)),
|
||||
],
|
||||
)
|
||||
|
@ -128,7 +128,7 @@ class _PermissionManagementState extends State<PermissionManagement> {
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
option.title,
|
||||
' ${option.title.isNotEmpty ? option.title[0].toUpperCase() : ''}${option.title.substring(1)}',
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 12,
|
||||
@ -184,7 +184,7 @@ class _PermissionManagementState extends State<PermissionManagement> {
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
subOption.title,
|
||||
' ${subOption.title.isNotEmpty ? subOption.title[0].toUpperCase() : ''}${subOption.title.substring(1)}',
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 12,
|
||||
@ -246,7 +246,7 @@ class _PermissionManagementState extends State<PermissionManagement> {
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
child.title,
|
||||
' ${child.title.isNotEmpty ? child.title[0].toUpperCase() : ''}${child.title.substring(1)}',
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 12,
|
||||
|
@ -5,141 +5,156 @@ import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
Future<void> showPopUpFilterMenu({
|
||||
required BuildContext context,
|
||||
Function()? onSortAtoZ,
|
||||
Function()? onSortZtoA,
|
||||
required Function(String value) onSortAtoZ,
|
||||
required Function(String value) onSortZtoA,
|
||||
Function()? cancelButton,
|
||||
required Map<String, bool> checkboxStates,
|
||||
required RelativeRect position,
|
||||
Function()? onOkPressed,
|
||||
List<String>? list,
|
||||
String? isSelected,
|
||||
}) async {
|
||||
|
||||
|
||||
await showMenu(
|
||||
context: context,
|
||||
position:position,
|
||||
|
||||
|
||||
position: position,
|
||||
color: ColorsManager.whiteColors,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
items: <PopupMenuEntry>[
|
||||
PopupMenuItem(
|
||||
onTap: onSortAtoZ,
|
||||
child: ListTile(
|
||||
leading: Image.asset(
|
||||
Assets.AtoZIcon,
|
||||
width: 25,
|
||||
),
|
||||
title: const Text(
|
||||
"Sort A to Z",
|
||||
style: TextStyle(color: Colors.blueGrey),
|
||||
),
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: onSortZtoA,
|
||||
child: ListTile(
|
||||
leading: Image.asset(
|
||||
Assets.ZtoAIcon,
|
||||
width: 25,
|
||||
),
|
||||
title: const Text(
|
||||
"Sort Z to A",
|
||||
style: TextStyle(color: Colors.blueGrey),
|
||||
),
|
||||
),
|
||||
),
|
||||
const PopupMenuDivider(),
|
||||
const PopupMenuItem(
|
||||
child: Text(
|
||||
"Filter by Status",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
)
|
||||
// Container(
|
||||
// decoration: containerDecoration.copyWith(
|
||||
// boxShadow: [],
|
||||
// borderRadius: const BorderRadius.only(
|
||||
// topLeft: Radius.circular(10), topRight: Radius.circular(10))),
|
||||
// child: Padding(
|
||||
// padding: const EdgeInsets.all(8.0),
|
||||
// child: TextFormField(
|
||||
// onChanged: onTextFieldChanged,
|
||||
// style: const TextStyle(color: Colors.black),
|
||||
// decoration: textBoxDecoration(radios: 15)!.copyWith(
|
||||
// fillColor: ColorsManager.whiteColors,
|
||||
// errorStyle: const TextStyle(height: 0),
|
||||
// hintStyle: context.textTheme.titleSmall?.copyWith(
|
||||
// color: Colors.grey,
|
||||
// fontSize: 12,
|
||||
// ),
|
||||
// hintText: 'Search',
|
||||
// suffixIcon: SizedBox(
|
||||
// child: SvgPicture.asset(
|
||||
// Assets.searchIconUser,
|
||||
// fit: BoxFit.none,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// // "Filter by Status",
|
||||
// // style: TextStyle(fontWeight: FontWeight.bold),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Container(
|
||||
decoration: containerDecoration.copyWith(
|
||||
boxShadow: [],
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(10),
|
||||
bottomRight: Radius.circular(10))),
|
||||
padding: const EdgeInsets.all(10),
|
||||
height: 200,
|
||||
width: 400,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
color: Colors.white,
|
||||
child: ListView.builder(
|
||||
itemCount: list?.length ?? 0,
|
||||
itemBuilder: (context, index) {
|
||||
final item = list![index];
|
||||
return CheckboxListTile(
|
||||
dense: true,
|
||||
title: Text(item),
|
||||
value: checkboxStates[item],
|
||||
onChanged: (bool? newValue) {
|
||||
checkboxStates[item] = newValue ?? false;
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop(); // Close the menu
|
||||
},
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: onOkPressed,
|
||||
child: const Text(
|
||||
"OK",
|
||||
style: TextStyle(
|
||||
color: ColorsManager.spaceColor,
|
||||
enabled: false,
|
||||
child: StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
child: ListTile(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
if (isSelected == 'Asc') {
|
||||
isSelected = null;
|
||||
onSortAtoZ.call('');
|
||||
} else {
|
||||
onSortAtoZ.call('Asc');
|
||||
isSelected = 'Asc';
|
||||
}
|
||||
});
|
||||
},
|
||||
leading: Image.asset(
|
||||
Assets.AtoZIcon,
|
||||
width: 25,
|
||||
),
|
||||
title: Text(
|
||||
"Sort A to Z",
|
||||
style: TextStyle(
|
||||
color: isSelected == "Asc"
|
||||
? ColorsManager.blackColor
|
||||
: ColorsManager.grayColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
ListTile(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
if (isSelected == 'Desc') {
|
||||
isSelected = null;
|
||||
onSortZtoA.call('');
|
||||
} else {
|
||||
onSortZtoA.call('Desc');
|
||||
isSelected = 'Desc';
|
||||
}
|
||||
});
|
||||
},
|
||||
leading: Image.asset(
|
||||
Assets.ZtoAIcon,
|
||||
width: 25,
|
||||
),
|
||||
title: Text(
|
||||
"Sort Z to A",
|
||||
style: TextStyle(
|
||||
color: isSelected == "Desc"
|
||||
? ColorsManager.blackColor
|
||||
: ColorsManager.grayColor),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
const Text(
|
||||
"Filter by Status",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Container(
|
||||
decoration: containerDecoration.copyWith(
|
||||
boxShadow: [],
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(10),
|
||||
topRight: Radius.circular(10),
|
||||
bottomLeft: Radius.circular(10),
|
||||
bottomRight: Radius.circular(10))),
|
||||
padding: const EdgeInsets.all(10),
|
||||
height: 200,
|
||||
width: 400,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
color: Colors.white,
|
||||
child: ListView.builder(
|
||||
itemCount: list?.length ?? 0,
|
||||
itemBuilder: (context, index) {
|
||||
final item = list![index];
|
||||
return Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: checkboxStates[item],
|
||||
onChanged: (bool? newValue) {
|
||||
checkboxStates[item] = newValue ?? false;
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
),
|
||||
Text(
|
||||
item,
|
||||
style: TextStyle(color: ColorsManager.grayColor),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
const Divider(),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop(); // Close the menu
|
||||
},
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: onOkPressed,
|
||||
child: const Text(
|
||||
"OK",
|
||||
style: TextStyle(
|
||||
color: ColorsManager.spaceColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -52,14 +52,16 @@ class _RoleDropdownState extends State<RoleDropdown> {
|
||||
SizedBox(
|
||||
child: DropdownButtonFormField<String>(
|
||||
dropdownColor: ColorsManager.whiteColors,
|
||||
alignment: Alignment.center,
|
||||
// alignment: Alignment.,
|
||||
focusColor: Colors.white,
|
||||
autofocus: true,
|
||||
value: selectedRole.isNotEmpty ? selectedRole : null,
|
||||
items: widget.bloc!.roles.map((role) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: role.uuid,
|
||||
child: Text(role.type),
|
||||
child: Text(
|
||||
' ${role.type.isNotEmpty ? role.type[0].toUpperCase() : ''}${role.type.substring(1)}',
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (value) {
|
||||
|
@ -93,7 +93,7 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
|
||||
try {
|
||||
emit(UsersLoadingState());
|
||||
bool res = await UserPermissionApi().changeUserStatusById(
|
||||
event.userId, event.newStatus == "disabled" ? true : false);
|
||||
event.userId, event.newStatus == "disabled" ? false : true);
|
||||
if (res == true) {
|
||||
add(const GetUsers());
|
||||
// users = users.map((user) {
|
||||
@ -133,7 +133,10 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
|
||||
} else {
|
||||
emit(UsersLoadingState());
|
||||
currentSortOrder = "Asc";
|
||||
users.sort((a, b) => a.firstName!.compareTo(b.firstName!));
|
||||
users.sort((a, b) => a.firstName
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.compareTo(b.firstName.toString().toLowerCase()));
|
||||
emit(UsersLoadedState(users: users));
|
||||
}
|
||||
}
|
||||
@ -164,6 +167,7 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
|
||||
emit(UsersLoadedState(users: users));
|
||||
} else {
|
||||
emit(UsersLoadingState());
|
||||
currentSortOrder = "NewestToOldest";
|
||||
users.sort((a, b) {
|
||||
final dateA = _parseDateTime(a.createdDate);
|
||||
final dateB = _parseDateTime(b.createdDate);
|
||||
@ -188,6 +192,7 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
|
||||
final dateB = _parseDateTime(b.createdDate);
|
||||
return dateA.compareTo(dateB);
|
||||
});
|
||||
currentSortOrder = "OldestToNewest";
|
||||
emit(UsersLoadedState(users: users));
|
||||
}
|
||||
}
|
||||
@ -210,7 +215,7 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
|
||||
final query = event.query.toLowerCase();
|
||||
final filteredUsers = initialUsers.where((user) {
|
||||
final fullName = "${user.firstName} ${user.lastName}".toLowerCase();
|
||||
final email = user.email.toLowerCase() ;
|
||||
final email = user.email.toLowerCase();
|
||||
return fullName.contains(query) || email.contains(query);
|
||||
}).toList();
|
||||
emit(UsersLoadedState(users: filteredUsers));
|
||||
@ -256,49 +261,96 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
|
||||
|
||||
void _filterUsersByRole(
|
||||
FilterUsersByRoleEvent event, Emitter<UserTableState> emit) {
|
||||
selectedRoles = event.selectedRoles.toSet();
|
||||
selectedRoles = event.selectedRoles!.toSet();
|
||||
|
||||
final filteredUsers = initialUsers.where((user) {
|
||||
if (selectedRoles.isEmpty) return true;
|
||||
return selectedRoles.contains(user.roleType);
|
||||
}).toList();
|
||||
|
||||
if (event.sortOrder == "Asc") {
|
||||
currentSortOrder = "Asc";
|
||||
filteredUsers.sort((a, b) => a.firstName
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.compareTo(b.firstName.toString().toLowerCase()));
|
||||
} else if (event.sortOrder == "Desc") {
|
||||
currentSortOrder = "Desc";
|
||||
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
|
||||
} else {
|
||||
currentSortOrder = "";
|
||||
}
|
||||
|
||||
emit(UsersLoadedState(users: filteredUsers));
|
||||
}
|
||||
|
||||
void _filterUsersByJobTitle(
|
||||
FilterUsersByJobEvent event, Emitter<UserTableState> emit) {
|
||||
selectedJobTitles = event.selectedJob.toSet();
|
||||
|
||||
selectedJobTitles = event.selectedJob!.toSet();
|
||||
emit(UsersLoadingState());
|
||||
final filteredUsers = initialUsers.where((user) {
|
||||
if (selectedJobTitles.isEmpty) return true;
|
||||
return selectedJobTitles.contains(user.jobTitle);
|
||||
}).toList();
|
||||
|
||||
if (event.sortOrder == "Asc") {
|
||||
currentSortOrder = "Asc";
|
||||
filteredUsers.sort((a, b) => a.firstName
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.compareTo(b.firstName.toString().toLowerCase()));
|
||||
} else if (event.sortOrder == "Desc") {
|
||||
currentSortOrder = "Desc";
|
||||
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
|
||||
} else {
|
||||
currentSortOrder = "";
|
||||
}
|
||||
emit(UsersLoadedState(users: filteredUsers));
|
||||
}
|
||||
|
||||
void _filterUsersByCreated(
|
||||
FilterUsersByCreatedEvent event, Emitter<UserTableState> emit) {
|
||||
selectedCreatedBy = event.selectedCreatedBy.toSet();
|
||||
selectedCreatedBy = event.selectedCreatedBy!.toSet();
|
||||
|
||||
final filteredUsers = initialUsers.where((user) {
|
||||
if (selectedCreatedBy.isEmpty) return true;
|
||||
return selectedCreatedBy.contains(user.invitedBy);
|
||||
}).toList();
|
||||
|
||||
if (event.sortOrder == "Asc") {
|
||||
currentSortOrder = "Asc";
|
||||
filteredUsers.sort((a, b) => a.firstName
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.compareTo(b.firstName.toString().toLowerCase()));
|
||||
} else if (event.sortOrder == "Desc") {
|
||||
currentSortOrder = "Desc";
|
||||
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
|
||||
} else {
|
||||
currentSortOrder = "";
|
||||
}
|
||||
emit(UsersLoadedState(users: filteredUsers));
|
||||
}
|
||||
|
||||
void _filterUserStatus(
|
||||
FilterUsersByDeActevateEvent event, Emitter<UserTableState> emit) {
|
||||
selectedStatuses = event.selectedActivate.toSet();
|
||||
selectedStatuses = event.selectedActivate!.toSet();
|
||||
|
||||
final filteredUsers = initialUsers.where((user) {
|
||||
if (selectedStatuses.isEmpty) return true;
|
||||
return selectedStatuses.contains(user.status);
|
||||
}).toList();
|
||||
|
||||
if (event.sortOrder == "Asc") {
|
||||
currentSortOrder = "Asc";
|
||||
filteredUsers.sort((a, b) => a.firstName
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.compareTo(b.firstName.toString().toLowerCase()));
|
||||
} else if (event.sortOrder == "Desc") {
|
||||
currentSortOrder = "Desc";
|
||||
filteredUsers.sort((a, b) => b.firstName!.compareTo(a.firstName!));
|
||||
} else {
|
||||
currentSortOrder = "";
|
||||
}
|
||||
emit(UsersLoadedState(users: filteredUsers));
|
||||
}
|
||||
|
||||
|
@ -89,35 +89,36 @@ class DeleteUserEvent extends UserTableEvent {
|
||||
}
|
||||
|
||||
class FilterUsersByRoleEvent extends UserTableEvent {
|
||||
final List<String> selectedRoles;
|
||||
final List<String>? selectedRoles;
|
||||
final String? sortOrder;
|
||||
|
||||
FilterUsersByRoleEvent(this.selectedRoles);
|
||||
@override
|
||||
List<Object?> get props => [selectedRoles];
|
||||
const FilterUsersByRoleEvent({this.selectedRoles, this.sortOrder});
|
||||
List<Object?> get props => [selectedRoles, sortOrder];
|
||||
}
|
||||
|
||||
class FilterUsersByJobEvent extends UserTableEvent {
|
||||
final List<String> selectedJob;
|
||||
final List<String>? selectedJob;
|
||||
final String? sortOrder;
|
||||
|
||||
FilterUsersByJobEvent(this.selectedJob);
|
||||
@override
|
||||
List<Object?> get props => [selectedJob];
|
||||
const FilterUsersByJobEvent({this.selectedJob, this.sortOrder});
|
||||
List<Object?> get props => [selectedJob, sortOrder];
|
||||
}
|
||||
|
||||
class FilterUsersByCreatedEvent extends UserTableEvent {
|
||||
final List<String> selectedCreatedBy;
|
||||
final List<String>? selectedCreatedBy;
|
||||
|
||||
FilterUsersByCreatedEvent(this.selectedCreatedBy);
|
||||
@override
|
||||
List<Object?> get props => [selectedCreatedBy];
|
||||
final String? sortOrder;
|
||||
|
||||
const FilterUsersByCreatedEvent({this.selectedCreatedBy, this.sortOrder});
|
||||
List<Object?> get props => [selectedCreatedBy, sortOrder];
|
||||
}
|
||||
|
||||
class FilterUsersByDeActevateEvent extends UserTableEvent {
|
||||
final List<String> selectedActivate;
|
||||
final List<String>? selectedActivate;
|
||||
final String? sortOrder;
|
||||
|
||||
FilterUsersByDeActevateEvent(this.selectedActivate);
|
||||
@override
|
||||
List<Object?> get props => [selectedActivate];
|
||||
const FilterUsersByDeActevateEvent({this.selectedActivate, this.sortOrder});
|
||||
List<Object?> get props => [selectedActivate, sortOrder];
|
||||
}
|
||||
|
||||
class FilterOptionsEvent extends UserTableEvent {
|
||||
|
@ -19,19 +19,13 @@ class DynamicTableScreen extends StatefulWidget {
|
||||
class _DynamicTableScreenState extends State<DynamicTableScreen>
|
||||
with WidgetsBindingObserver {
|
||||
late List<double> columnWidths;
|
||||
late double totalWidth;
|
||||
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
// // Initialize column widths with default sizes proportional to the screen width
|
||||
// // Assigning placeholder values here. The actual sizes will be updated in `build`.
|
||||
// }
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
setState(() {
|
||||
columnWidths = List<double>.filled(widget.titles.length, 150.0);
|
||||
});
|
||||
columnWidths = List<double>.filled(widget.titles.length, 150.0);
|
||||
totalWidth = columnWidths.reduce((a, b) => a + b);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
@ -44,7 +38,6 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
|
||||
@override
|
||||
void didChangeMetrics() {
|
||||
super.didChangeMetrics();
|
||||
// Screen size might have changed
|
||||
final newScreenWidth = MediaQuery.of(context).size.width;
|
||||
setState(() {
|
||||
columnWidths = List<double>.generate(widget.titles.length, (index) {
|
||||
@ -53,7 +46,7 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
|
||||
0.12; // 20% of screen width for the second column
|
||||
} else if (index == 9) {
|
||||
return newScreenWidth *
|
||||
0.2; // 25% of screen width for the tenth column
|
||||
0.1; // 25% of screen width for the tenth column
|
||||
}
|
||||
return newScreenWidth *
|
||||
0.09; // Default to 10% of screen width for other columns
|
||||
@ -64,293 +57,204 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
// Initialize column widths if they are still set to placeholder values
|
||||
if (columnWidths.every((width) => width == 120.0)) {
|
||||
if (columnWidths.every((width) => width == screenWidth * 7)) {
|
||||
columnWidths = List<double>.generate(widget.titles.length, (index) {
|
||||
if (index == 1) {
|
||||
return screenWidth * 0.11;
|
||||
} else if (index == 9) {
|
||||
return screenWidth * 0.2;
|
||||
return screenWidth * 0.1;
|
||||
}
|
||||
return screenWidth * 0.11;
|
||||
return screenWidth * 0.09;
|
||||
});
|
||||
setState(() {});
|
||||
}
|
||||
return Container(
|
||||
child: SingleChildScrollView(
|
||||
clipBehavior: Clip.none,
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Container(
|
||||
decoration: containerDecoration.copyWith(
|
||||
color: ColorsManager.whiteColors,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20))),
|
||||
child: FittedBox(
|
||||
child: Column(
|
||||
children: [
|
||||
// Header Row with Resizable Columns
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
decoration: containerDecoration.copyWith(
|
||||
color: ColorsManager.circleRolesBackground,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(15),
|
||||
topRight: Radius.circular(15))),
|
||||
child: Row(
|
||||
children: List.generate(widget.titles.length, (index) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
FittedBox(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(left: 5, right: 5),
|
||||
width: columnWidths[index],
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
child: Text(
|
||||
widget.titles[index],
|
||||
maxLines: 2,
|
||||
style: const TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 13,
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
return SingleChildScrollView(
|
||||
clipBehavior: Clip.none,
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Container(
|
||||
decoration: containerDecoration.copyWith(
|
||||
color: ColorsManager.whiteColors,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20))),
|
||||
child: FittedBox(
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: totalWidth,
|
||||
decoration: containerDecoration.copyWith(
|
||||
color: ColorsManager.circleRolesBackground,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(15),
|
||||
topRight: Radius.circular(15))),
|
||||
child: Row(
|
||||
children: List.generate(widget.titles.length, (index) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
FittedBox(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(left: 5, right: 5),
|
||||
width: columnWidths[index],
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
child: Text(
|
||||
widget.titles[index],
|
||||
maxLines: 2,
|
||||
style: const TextStyle(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 13,
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
),
|
||||
if (index != 1 &&
|
||||
index != 9 &&
|
||||
index != 8 &&
|
||||
index != 5)
|
||||
FittedBox(
|
||||
child: IconButton(
|
||||
icon: SvgPicture.asset(
|
||||
Assets.filterTableIcon,
|
||||
fit: BoxFit.none,
|
||||
),
|
||||
onPressed: () {
|
||||
if (widget.onFilter != null) {
|
||||
widget.onFilter!(index);
|
||||
}
|
||||
},
|
||||
),
|
||||
if (index != 1 &&
|
||||
index != 9 &&
|
||||
index != 8 &&
|
||||
index != 5)
|
||||
FittedBox(
|
||||
child: IconButton(
|
||||
icon: SvgPicture.asset(
|
||||
Assets.filterTableIcon,
|
||||
fit: BoxFit.none,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onHorizontalDragUpdate: (details) {
|
||||
setState(() {
|
||||
columnWidths[index] = (columnWidths[index] +
|
||||
details.delta.dx)
|
||||
.clamp(
|
||||
150.0, 300.0); // Minimum & Maximum size
|
||||
});
|
||||
},
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors
|
||||
.resizeColumn, // Set the cursor to resize
|
||||
child: Container(
|
||||
color: Colors.green,
|
||||
child: Container(
|
||||
color: ColorsManager.boxDivider,
|
||||
width: 1,
|
||||
height: 50, // Height of the header cell
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
// Data Rows with Dividers
|
||||
widget.rows.isEmpty
|
||||
? Container(
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).size.height / 2,
|
||||
child: Container(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
SvgPicture.asset(Assets.emptyTable),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
onPressed: () {
|
||||
if (widget.onFilter != null) {
|
||||
widget.onFilter!(index);
|
||||
}
|
||||
},
|
||||
),
|
||||
const Text(
|
||||
'No Users',
|
||||
style: TextStyle(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Center(
|
||||
child: Container(
|
||||
// height: MediaQuery.of(context).size.height * 0.59,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
decoration: containerDecoration.copyWith(
|
||||
color: ColorsManager.whiteColors,
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(15),
|
||||
bottomRight: Radius.circular(15))),
|
||||
child: ListView.builder(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemCount: widget.rows.length,
|
||||
itemBuilder: (context, rowIndex) {
|
||||
if (columnWidths
|
||||
.every((width) => width == 120.0)) {
|
||||
columnWidths = List<double>.generate(
|
||||
widget.titles.length, (index) {
|
||||
if (index == 1) {
|
||||
return screenWidth * 0.11;
|
||||
} else if (index == 9) {
|
||||
return screenWidth * 0.2;
|
||||
}
|
||||
GestureDetector(
|
||||
onHorizontalDragUpdate: (details) {
|
||||
setState(() {
|
||||
columnWidths[index] =
|
||||
(columnWidths[index] + details.delta.dx)
|
||||
.clamp(150.0, 300.0);
|
||||
totalWidth = columnWidths.reduce((a, b) => a + b);
|
||||
});
|
||||
},
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.resizeColumn,
|
||||
child: Container(
|
||||
color: Colors.green,
|
||||
child: Container(
|
||||
color: ColorsManager.boxDivider,
|
||||
width: 1,
|
||||
height: 50,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
widget.rows.isEmpty
|
||||
? SizedBox(
|
||||
height: MediaQuery.of(context).size.height / 2,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
SvgPicture.asset(Assets.emptyTable),
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
const Text(
|
||||
'No Users',
|
||||
style: TextStyle(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Center(
|
||||
child: Container(
|
||||
width: totalWidth,
|
||||
decoration: containerDecoration.copyWith(
|
||||
color: ColorsManager.whiteColors,
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(15),
|
||||
bottomRight: Radius.circular(15))),
|
||||
child: ListView.builder(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemCount: widget.rows.length,
|
||||
itemBuilder: (context, rowIndex) {
|
||||
if (columnWidths.every((width) => width == 120.0)) {
|
||||
columnWidths = List<double>.generate(
|
||||
widget.titles.length, (index) {
|
||||
if (index == 1) {
|
||||
return screenWidth * 0.11;
|
||||
});
|
||||
setState(() {});
|
||||
}
|
||||
final row = widget.rows[rowIndex];
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 5,
|
||||
top: 10,
|
||||
right: 5,
|
||||
bottom: 10),
|
||||
child: Row(
|
||||
children:
|
||||
List.generate(row.length, (index) {
|
||||
return SizedBox(
|
||||
width: columnWidths[index],
|
||||
child: SizedBox(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 15, right: 10),
|
||||
child: row[index],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (rowIndex < widget.rows.length - 1)
|
||||
Row(
|
||||
children: List.generate(
|
||||
widget.titles.length, (index) {
|
||||
} else if (index == 9) {
|
||||
return screenWidth * 0.2;
|
||||
}
|
||||
return screenWidth * 0.11;
|
||||
});
|
||||
setState(() {});
|
||||
}
|
||||
final row = widget.rows[rowIndex];
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 5, top: 10, right: 5, bottom: 10),
|
||||
child: Row(
|
||||
children:
|
||||
List.generate(row.length, (index) {
|
||||
return SizedBox(
|
||||
width: columnWidths[index],
|
||||
child: const Divider(
|
||||
color: ColorsManager.boxDivider,
|
||||
thickness: 1,
|
||||
height: 1,
|
||||
child: SizedBox(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 15, right: 10),
|
||||
child: row[index],
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
if (rowIndex < widget.rows.length - 1)
|
||||
Row(
|
||||
children: List.generate(
|
||||
widget.titles.length, (index) {
|
||||
return SizedBox(
|
||||
width: columnWidths[index],
|
||||
child: const Divider(
|
||||
color: ColorsManager.boxDivider,
|
||||
thickness: 1,
|
||||
height: 1,
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Widget build(BuildContext context) {
|
||||
// return Scaffold(
|
||||
// body: SingleChildScrollView(
|
||||
// scrollDirection: Axis.horizontal,
|
||||
// child: SingleChildScrollView(
|
||||
// scrollDirection: Axis.vertical,
|
||||
// child: Column(
|
||||
// children: [
|
||||
// // Header Row with Resizable Columns
|
||||
// Container(
|
||||
// color: Colors.green,
|
||||
// child: Row(
|
||||
// children: List.generate(widget.titles.length, (index) {
|
||||
// return Row(
|
||||
// children: [
|
||||
// Container(
|
||||
// width: columnWidths[index],
|
||||
// decoration: const BoxDecoration(
|
||||
// color: Colors.green,
|
||||
// ),
|
||||
// child: Text(
|
||||
// widget.titles[index],
|
||||
// style: TextStyle(fontWeight: FontWeight.bold),
|
||||
// textAlign: TextAlign.center,
|
||||
// ),
|
||||
// ),
|
||||
// GestureDetector(
|
||||
// onHorizontalDragUpdate: (details) {
|
||||
// setState(() {
|
||||
// columnWidths[index] = (columnWidths[index] +
|
||||
// details.delta.dx)
|
||||
// .clamp(50.0, 300.0); // Minimum & Maximum size
|
||||
// });
|
||||
// },
|
||||
// child: MouseRegion(
|
||||
// cursor: SystemMouseCursors
|
||||
// .resizeColumn, // Set the cursor to resize
|
||||
// child: Container(
|
||||
// color: Colors.green,
|
||||
// child: Container(
|
||||
// color: Colors.black,
|
||||
// width: 1,
|
||||
// height: 50, // Height of the header cell
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
// }),
|
||||
// ),
|
||||
// ),
|
||||
// // Data Rows
|
||||
// ...widget.rows.map((row) {
|
||||
// return Row(
|
||||
// children: List.generate(row.length, (index) {
|
||||
// return Container(
|
||||
// width: columnWidths[index],
|
||||
// child: row[index],
|
||||
// );
|
||||
// }),
|
||||
// );
|
||||
// }).toList(),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
@ -107,7 +107,6 @@ class UsersPage extends StatelessWidget {
|
||||
builder: (context, state) {
|
||||
final screenSize = MediaQuery.of(context).size;
|
||||
final _blocRole = BlocProvider.of<UserTableBloc>(context);
|
||||
|
||||
if (state is UsersLoadingState) {
|
||||
_blocRole.add(ChangePage(_blocRole.currentPage));
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
@ -189,8 +188,6 @@ class UsersPage extends StatelessWidget {
|
||||
const SizedBox(height: 25),
|
||||
DynamicTableScreen(
|
||||
onFilter: (columnIndex) {
|
||||
_blocRole.add(FilterClearEvent());
|
||||
|
||||
if (columnIndex == 0) {
|
||||
showNameMenu(
|
||||
context: context,
|
||||
@ -210,11 +207,12 @@ class UsersPage extends StatelessWidget {
|
||||
if (columnIndex == 2) {
|
||||
final Map<String, bool> checkboxStates = {
|
||||
for (var item in _blocRole.jobTitle)
|
||||
item: false, // Initialize with false
|
||||
item: _blocRole.selectedJobTitles.contains(item),
|
||||
};
|
||||
final RenderBox overlay = Overlay.of(context)
|
||||
.context
|
||||
.findRenderObject() as RenderBox;
|
||||
|
||||
showPopUpFilterMenu(
|
||||
position: RelativeRect.fromLTRB(
|
||||
overlay.size.width / 4,
|
||||
@ -225,26 +223,28 @@ class UsersPage extends StatelessWidget {
|
||||
list: _blocRole.jobTitle,
|
||||
context: context,
|
||||
checkboxStates: checkboxStates,
|
||||
isSelected: _blocRole.currentSortOrder,
|
||||
onOkPressed: () {
|
||||
_blocRole.add(FilterClearEvent());
|
||||
final selectedItems = checkboxStates.entries
|
||||
.where((entry) => entry.value)
|
||||
.map((entry) => entry.key)
|
||||
.toList();
|
||||
Navigator.of(context).pop();
|
||||
_blocRole.add(FilterUsersByJobEvent(selectedItems));
|
||||
_blocRole.add(FilterUsersByJobEvent(
|
||||
selectedJob: selectedItems,
|
||||
sortOrder: _blocRole.currentSortOrder,
|
||||
));
|
||||
},
|
||||
onSortAtoZ: () {
|
||||
context
|
||||
.read<UserTableBloc>()
|
||||
.add(const SortUsersByNameAsc());
|
||||
onSortAtoZ: (v) {
|
||||
_blocRole.currentSortOrder = v;
|
||||
},
|
||||
onSortZtoA: () {
|
||||
context
|
||||
.read<UserTableBloc>()
|
||||
.add(const SortUsersByNameDesc());
|
||||
onSortZtoA: (v) {
|
||||
_blocRole.currentSortOrder = v;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (columnIndex == 3) {
|
||||
final Map<String, bool> checkboxStates = {
|
||||
for (var item in _blocRole.roleTypes)
|
||||
@ -263,32 +263,31 @@ class UsersPage extends StatelessWidget {
|
||||
list: _blocRole.roleTypes,
|
||||
context: context,
|
||||
checkboxStates: checkboxStates,
|
||||
isSelected: _blocRole.currentSortOrder,
|
||||
onOkPressed: () {
|
||||
_blocRole.add(FilterClearEvent());
|
||||
final selectedItems = checkboxStates.entries
|
||||
.where((entry) => entry.value)
|
||||
.map((entry) => entry.key)
|
||||
.toList();
|
||||
Navigator.of(context).pop();
|
||||
context
|
||||
.read<UserTableBloc>()
|
||||
.add(FilterUsersByRoleEvent(selectedItems));
|
||||
context.read<UserTableBloc>().add(
|
||||
FilterUsersByRoleEvent(
|
||||
selectedRoles: selectedItems,
|
||||
sortOrder: _blocRole.currentSortOrder));
|
||||
},
|
||||
onSortAtoZ: () {
|
||||
context
|
||||
.read<UserTableBloc>()
|
||||
.add(const SortUsersByNameAsc());
|
||||
onSortAtoZ: (v) {
|
||||
_blocRole.currentSortOrder = v;
|
||||
},
|
||||
onSortZtoA: () {
|
||||
context
|
||||
.read<UserTableBloc>()
|
||||
.add(const SortUsersByNameDesc());
|
||||
onSortZtoA: (v) {
|
||||
_blocRole.currentSortOrder = v;
|
||||
},
|
||||
);
|
||||
}
|
||||
if (columnIndex == 4) {
|
||||
showDateFilterMenu(
|
||||
context: context,
|
||||
isSelected: _blocRole.currentSortOrderDate,
|
||||
isSelected: _blocRole.currentSortOrder,
|
||||
aToZTap: () {
|
||||
context
|
||||
.read<UserTableBloc>()
|
||||
@ -319,32 +318,30 @@ class UsersPage extends StatelessWidget {
|
||||
list: _blocRole.createdBy,
|
||||
context: context,
|
||||
checkboxStates: checkboxStates,
|
||||
isSelected: _blocRole.currentSortOrder,
|
||||
onOkPressed: () {
|
||||
_blocRole.add(FilterClearEvent());
|
||||
final selectedItems = checkboxStates.entries
|
||||
.where((entry) => entry.value)
|
||||
.map((entry) => entry.key)
|
||||
.toList();
|
||||
Navigator.of(context).pop();
|
||||
_blocRole
|
||||
.add(FilterUsersByCreatedEvent(selectedItems));
|
||||
_blocRole.add(FilterUsersByCreatedEvent(
|
||||
selectedCreatedBy: selectedItems,
|
||||
sortOrder: _blocRole.currentSortOrder));
|
||||
},
|
||||
onSortAtoZ: () {
|
||||
context
|
||||
.read<UserTableBloc>()
|
||||
.add(const SortUsersByNameAsc());
|
||||
onSortAtoZ: (v) {
|
||||
_blocRole.currentSortOrder = v;
|
||||
},
|
||||
onSortZtoA: () {
|
||||
context
|
||||
.read<UserTableBloc>()
|
||||
.add(const SortUsersByNameDesc());
|
||||
onSortZtoA: (v) {
|
||||
_blocRole.currentSortOrder = v;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (columnIndex == 7) {
|
||||
final Map<String, bool> checkboxStates = {
|
||||
for (var item in _blocRole.status)
|
||||
item: _blocRole.selectedCreatedBy.contains(item),
|
||||
item: _blocRole.selectedStatuses.contains(item),
|
||||
};
|
||||
final RenderBox overlay = Overlay.of(context)
|
||||
.context
|
||||
@ -359,24 +356,24 @@ class UsersPage extends StatelessWidget {
|
||||
list: _blocRole.status,
|
||||
context: context,
|
||||
checkboxStates: checkboxStates,
|
||||
isSelected: _blocRole.currentSortOrder,
|
||||
onOkPressed: () {
|
||||
_blocRole.add(FilterClearEvent());
|
||||
|
||||
final selectedItems = checkboxStates.entries
|
||||
.where((entry) => entry.value)
|
||||
.map((entry) => entry.key)
|
||||
.toList();
|
||||
Navigator.of(context).pop();
|
||||
_blocRole
|
||||
.add(FilterUsersByCreatedEvent(selectedItems));
|
||||
_blocRole.add(FilterUsersByDeActevateEvent(
|
||||
selectedActivate: selectedItems,
|
||||
sortOrder: _blocRole.currentSortOrder));
|
||||
},
|
||||
onSortAtoZ: () {
|
||||
context
|
||||
.read<UserTableBloc>()
|
||||
.add(const SortUsersByNameAsc());
|
||||
onSortAtoZ: (v) {
|
||||
_blocRole.currentSortOrder = v;
|
||||
},
|
||||
onSortZtoA: () {
|
||||
context
|
||||
.read<UserTableBloc>()
|
||||
.add(const SortUsersByNameDesc());
|
||||
onSortZtoA: (v) {
|
||||
_blocRole.currentSortOrder = v;
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -412,8 +409,8 @@ class UsersPage extends StatelessWidget {
|
||||
rows: state.users.map((user) {
|
||||
return [
|
||||
Text('${user.firstName} ${user.lastName}'),
|
||||
Text(user.email ),
|
||||
Text(user.jobTitle ?? ''),
|
||||
Text(user.email),
|
||||
Text(user.jobTitle ?? '-'),
|
||||
Text(user.roleType ?? ''),
|
||||
Text(user.createdDate ?? ''),
|
||||
Text(user.createdTime ?? ''),
|
||||
@ -476,11 +473,17 @@ class UsersPage extends StatelessWidget {
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return DeleteUserDialog(
|
||||
onTapDelete: () {
|
||||
onTapDelete: () async {
|
||||
try {
|
||||
_blocRole.add(DeleteUserEvent(
|
||||
user.uuid, context));
|
||||
},
|
||||
);
|
||||
await Future.delayed(
|
||||
const Duration(seconds: 2));
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
).then((v) {
|
||||
if (v != null) {
|
||||
@ -504,6 +507,7 @@ class UsersPage extends StatelessWidget {
|
||||
SizedBox(
|
||||
width: 500,
|
||||
child: NumberPagination(
|
||||
visiblePagesCount: 4,
|
||||
buttonRadius: 10,
|
||||
selectedButtonColor: ColorsManager.secondaryColor,
|
||||
buttonUnSelectedBorderColor:
|
||||
|
@ -1,38 +1,74 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_state.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart';
|
||||
|
||||
class AddDeviceTypeBloc
|
||||
extends Bloc<AddDeviceTypeEvent, List<SelectedProduct>> {
|
||||
AddDeviceTypeBloc(List<SelectedProduct> initialProducts)
|
||||
: super(initialProducts) {
|
||||
class AddDeviceTypeBloc extends Bloc<AddDeviceTypeEvent, AddDeviceState> {
|
||||
AddDeviceTypeBloc() : super(AddDeviceInitial()) {
|
||||
on<InitializeDevice>(_onInitializeTagModels);
|
||||
on<UpdateProductCountEvent>(_onUpdateProductCount);
|
||||
}
|
||||
|
||||
void _onUpdateProductCount(
|
||||
UpdateProductCountEvent event, Emitter<List<SelectedProduct>> emit) {
|
||||
final existingProduct = state.firstWhere(
|
||||
(p) => p.productId == event.productId,
|
||||
orElse: () => SelectedProduct(productId: event.productId, count: 0,productName: event.productName,product: event.product ),
|
||||
);
|
||||
void _onInitializeTagModels(
|
||||
InitializeDevice event, Emitter<AddDeviceState> emit) {
|
||||
emit(AddDeviceLoaded(
|
||||
selectedProducts: event.addedProducts,
|
||||
initialTag: event.initialTags,
|
||||
));
|
||||
}
|
||||
|
||||
if (event.count > 0) {
|
||||
if (!state.contains(existingProduct)) {
|
||||
emit([
|
||||
...state,
|
||||
SelectedProduct(productId: event.productId, count: event.count, productName: event.productName, product: event.product)
|
||||
]);
|
||||
void _onUpdateProductCount(
|
||||
UpdateProductCountEvent event, Emitter<AddDeviceState> emit) {
|
||||
final currentState = state;
|
||||
|
||||
if (currentState is AddDeviceLoaded) {
|
||||
final existingProduct = currentState.selectedProducts.firstWhere(
|
||||
(p) => p.productId == event.productId,
|
||||
orElse: () => SelectedProduct(
|
||||
productId: event.productId,
|
||||
count: 0,
|
||||
productName: event.productName,
|
||||
product: event.product,
|
||||
),
|
||||
);
|
||||
|
||||
List<SelectedProduct> updatedProducts;
|
||||
|
||||
if (event.count > 0) {
|
||||
if (!currentState.selectedProducts.contains(existingProduct)) {
|
||||
updatedProducts = [
|
||||
...currentState.selectedProducts,
|
||||
SelectedProduct(
|
||||
productId: event.productId,
|
||||
count: event.count,
|
||||
productName: event.productName,
|
||||
product: event.product,
|
||||
),
|
||||
];
|
||||
} else {
|
||||
updatedProducts = currentState.selectedProducts.map((p) {
|
||||
if (p.productId == event.productId) {
|
||||
return SelectedProduct(
|
||||
productId: p.productId,
|
||||
count: event.count,
|
||||
productName: p.productName,
|
||||
product: p.product,
|
||||
);
|
||||
}
|
||||
return p;
|
||||
}).toList();
|
||||
}
|
||||
} else {
|
||||
final updatedList = state.map((p) {
|
||||
if (p.productId == event.productId) {
|
||||
return SelectedProduct(productId: p.productId, count: event.count, productName: p.productName,product: p.product);
|
||||
}
|
||||
return p;
|
||||
}).toList();
|
||||
emit(updatedList);
|
||||
// Remove the product if the count is 0
|
||||
updatedProducts = currentState.selectedProducts
|
||||
.where((p) => p.productId != event.productId)
|
||||
.toList();
|
||||
}
|
||||
} else {
|
||||
emit(state.where((p) => p.productId != event.productId).toList());
|
||||
|
||||
// Emit the updated state
|
||||
emit(AddDeviceLoaded(
|
||||
selectedProducts: updatedProducts,
|
||||
initialTag: currentState.initialTag));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
import 'package:equatable/equatable.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';
|
||||
|
||||
abstract class AddDeviceState extends Equatable {
|
||||
const AddDeviceState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class AddDeviceInitial extends AddDeviceState {}
|
||||
|
||||
class AddDeviceLoading extends AddDeviceState {}
|
||||
|
||||
class AddDeviceLoaded extends AddDeviceState {
|
||||
final List<SelectedProduct> selectedProducts;
|
||||
final List<Tag> initialTag;
|
||||
|
||||
const AddDeviceLoaded({
|
||||
required this.selectedProducts,
|
||||
required this.initialTag,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [selectedProducts, initialTag];
|
||||
}
|
||||
|
||||
class AddDeviceError extends AddDeviceState {
|
||||
final String errorMessage;
|
||||
|
||||
const AddDeviceError(this.errorMessage);
|
||||
|
||||
@override
|
||||
List<Object> get props => [errorMessage];
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||
|
||||
abstract class AddDeviceTypeEvent extends Equatable {
|
||||
const AddDeviceTypeEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
@ -12,8 +16,26 @@ class UpdateProductCountEvent extends AddDeviceTypeEvent {
|
||||
final String productName;
|
||||
final ProductModel product;
|
||||
|
||||
UpdateProductCountEvent({required this.productId, required this.count, required this.productName, required this.product});
|
||||
UpdateProductCountEvent(
|
||||
{required this.productId,
|
||||
required this.count,
|
||||
required this.productName,
|
||||
required this.product});
|
||||
|
||||
@override
|
||||
List<Object> get props => [productId, count];
|
||||
}
|
||||
|
||||
|
||||
class InitializeDevice extends AddDeviceTypeEvent {
|
||||
final List<Tag> initialTags;
|
||||
final List<SelectedProduct> addedProducts;
|
||||
|
||||
const InitializeDevice({
|
||||
this.initialTags = const [],
|
||||
required this.addedProducts,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [initialTags, addedProducts];
|
||||
}
|
||||
|
@ -1,17 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_state.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.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/product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
|
||||
class AddDeviceTypeWidget extends StatelessWidget {
|
||||
final List<ProductModel>? products;
|
||||
final ValueChanged<List<SelectedProduct>>? onProductsSelected;
|
||||
@ -20,8 +23,7 @@ class AddDeviceTypeWidget extends StatelessWidget {
|
||||
final List<Tag>? spaceTags;
|
||||
final List<String>? allTags;
|
||||
final String spaceName;
|
||||
final Function(List<Tag>,List<SubspaceModel>?)? onSave;
|
||||
|
||||
final Function(List<Tag>, List<SubspaceModel>?)? onSave;
|
||||
|
||||
const AddDeviceTypeWidget(
|
||||
{super.key,
|
||||
@ -44,30 +46,45 @@ class AddDeviceTypeWidget extends StatelessWidget {
|
||||
: 3;
|
||||
|
||||
return BlocProvider(
|
||||
create: (_) => AddDeviceTypeBloc(initialSelectedProducts ?? []),
|
||||
create: (_) => AddDeviceTypeBloc()
|
||||
..add(InitializeDevice(
|
||||
initialTags: spaceTags ?? [],
|
||||
addedProducts: initialSelectedProducts ?? [],
|
||||
)),
|
||||
child: Builder(
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Add Devices'),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
content: SingleChildScrollView(
|
||||
child: Container(
|
||||
width: size.width * 0.9,
|
||||
height: size.height * 0.65,
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
child: ScrollableGridViewWidget(
|
||||
products: products, crossAxisCount: crossAxisCount),
|
||||
),
|
||||
content: BlocBuilder<AddDeviceTypeBloc, AddDeviceState>(
|
||||
builder: (context, state) {
|
||||
if (state is AddDeviceLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (state is AddDeviceLoaded) {
|
||||
return SingleChildScrollView(
|
||||
child: Container(
|
||||
width: size.width * 0.9,
|
||||
height: size.height * 0.65,
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
child: ScrollableGridViewWidget(
|
||||
products: products,
|
||||
crossAxisCount: crossAxisCount),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox();
|
||||
}),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
@ -78,43 +95,53 @@ class AddDeviceTypeWidget extends StatelessWidget {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
ActionButton(
|
||||
label: 'Continue',
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
foregroundColor: ColorsManager.whiteColors,
|
||||
onPressed: () async {
|
||||
final currentState =
|
||||
context.read<AddDeviceTypeBloc>().state;
|
||||
Navigator.of(context).pop();
|
||||
SizedBox(
|
||||
width: 140,
|
||||
child: BlocBuilder<AddDeviceTypeBloc, AddDeviceState>(
|
||||
builder: (context, state) {
|
||||
final isDisabled = state is AddDeviceLoaded &&
|
||||
state.selectedProducts.isEmpty;
|
||||
return DefaultButton(
|
||||
borderRadius: 10,
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
foregroundColor: isDisabled
|
||||
? ColorsManager.whiteColorsWithOpacity
|
||||
: ColorsManager.whiteColors,
|
||||
onPressed: () async {
|
||||
if (state is AddDeviceLoaded &&
|
||||
state.selectedProducts.isNotEmpty) {
|
||||
final initialTags =
|
||||
TagHelper.generateInitialForTags(
|
||||
spaceTags: spaceTags,
|
||||
subspaces: subspaces,
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
|
||||
if (currentState.isNotEmpty) {
|
||||
final initialTags = generateInitialTags(
|
||||
spaceTags: spaceTags,
|
||||
subspaces: subspaces,
|
||||
);
|
||||
|
||||
final dialogTitle = initialTags.isNotEmpty
|
||||
? 'Edit Device'
|
||||
: 'Assign Tags';
|
||||
await showDialog<bool>(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (context) => AssignTagDialog(
|
||||
products: products,
|
||||
subspaces: subspaces,
|
||||
addedProducts: currentState,
|
||||
allTags: allTags,
|
||||
spaceName: spaceName,
|
||||
initialTags: initialTags,
|
||||
title: dialogTitle,
|
||||
onSave: (tags,subspaces){
|
||||
onSave!(tags,subspaces);
|
||||
final dialogTitle = initialTags.isNotEmpty
|
||||
? 'Edit Device'
|
||||
: 'Assign Tags';
|
||||
await showDialog<bool>(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (context) => AssignTagDialog(
|
||||
products: products,
|
||||
subspaces: subspaces,
|
||||
addedProducts: state.selectedProducts,
|
||||
allTags: allTags,
|
||||
spaceName: spaceName,
|
||||
initialTags: initialTags,
|
||||
title: dialogTitle,
|
||||
onSave: (tags, subspaces) {
|
||||
onSave!(tags, subspaces);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
child: const Text('Next'),
|
||||
);
|
||||
},
|
||||
)),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
@ -48,6 +48,7 @@ class DeviceTypeTileWidget extends StatelessWidget {
|
||||
DeviceNameWidget(name: product.name),
|
||||
const SizedBox(height: 4),
|
||||
CounterWidget(
|
||||
isCreate: false,
|
||||
initialCount: selectedProduct.count,
|
||||
onCountChanged: (newCount) {
|
||||
context.read<AddDeviceTypeBloc>().add(
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_state.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/add_device_type/widgets/device_type_tile_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/selected_product_model.dart';
|
||||
@ -24,8 +25,11 @@ class ScrollableGridViewWidget extends StatelessWidget {
|
||||
return Scrollbar(
|
||||
controller: scrollController,
|
||||
thumbVisibility: true,
|
||||
child: BlocBuilder<AddDeviceTypeBloc, List<SelectedProduct>>(
|
||||
builder: (context, productCounts) {
|
||||
child: BlocBuilder<AddDeviceTypeBloc, AddDeviceState>(
|
||||
builder: (context, state) {
|
||||
final productCounts = state is AddDeviceLoaded
|
||||
? state.selectedProducts
|
||||
: <SelectedProduct>[];
|
||||
return GridView.builder(
|
||||
controller: scrollController,
|
||||
shrinkWrap: true,
|
||||
|
@ -420,9 +420,10 @@ class SpaceManagementBloc
|
||||
await _api.deleteSpace(communityUuid, parent.uuid!);
|
||||
}
|
||||
} catch (e) {
|
||||
rethrow; // Decide whether to stop execution or continue
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
orderedSpaces.removeWhere((space) => parentsToDelete.contains(space));
|
||||
|
||||
for (var space in orderedSpaces) {
|
||||
try {
|
||||
|
@ -27,8 +27,7 @@ class SpaceManagementLoaded extends SpaceManagementState {
|
||||
required this.products,
|
||||
this.selectedCommunity,
|
||||
this.selectedSpace,
|
||||
this.spaceModels
|
||||
});
|
||||
this.spaceModels});
|
||||
}
|
||||
|
||||
class SpaceModelManagenetLoaded extends SpaceManagementState {
|
||||
@ -38,14 +37,10 @@ class SpaceModelManagenetLoaded extends SpaceManagementState {
|
||||
class BlankState extends SpaceManagementState {
|
||||
final List<CommunityModel> communities;
|
||||
final List<ProductModel> products;
|
||||
List<SpaceTemplateModel>? spaceModels;
|
||||
List<SpaceTemplateModel>? spaceModels;
|
||||
|
||||
|
||||
BlankState({
|
||||
required this.communities,
|
||||
required this.products,
|
||||
this.spaceModels
|
||||
});
|
||||
BlankState(
|
||||
{required this.communities, required this.products, this.spaceModels});
|
||||
}
|
||||
|
||||
class SpaceCreationSuccess extends SpaceManagementState {
|
||||
@ -67,7 +62,7 @@ class SpaceManagementError extends SpaceManagementState {
|
||||
}
|
||||
|
||||
class SpaceModelLoaded extends SpaceManagementState {
|
||||
final List<SpaceTemplateModel> spaceModels;
|
||||
List<SpaceTemplateModel> spaceModels;
|
||||
final List<ProductModel> products;
|
||||
final List<CommunityModel> communities;
|
||||
|
||||
|
26
lib/pages/spaces_management/all_spaces/model/base_tag.dart
Normal file
26
lib/pages/spaces_management/all_spaces/model/base_tag.dart
Normal file
@ -0,0 +1,26 @@
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
abstract class BaseTag {
|
||||
String? uuid;
|
||||
String? tag;
|
||||
final ProductModel? product;
|
||||
String internalId;
|
||||
String? location;
|
||||
|
||||
BaseTag({
|
||||
this.uuid,
|
||||
required this.tag,
|
||||
this.product,
|
||||
String? internalId,
|
||||
this.location,
|
||||
}) : internalId = internalId ?? const Uuid().v4();
|
||||
|
||||
Map<String, dynamic> toJson();
|
||||
BaseTag copyWith({
|
||||
String? tag,
|
||||
ProductModel? product,
|
||||
String? location,
|
||||
String? internalId,
|
||||
});
|
||||
}
|
@ -66,4 +66,25 @@ class ProductModel {
|
||||
String toString() {
|
||||
return 'ProductModel(uuid: $uuid, catName: $catName, prodId: $prodId, prodType: $prodType, name: $name, icon: $icon)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ProductModel &&
|
||||
runtimeType == other.runtimeType &&
|
||||
uuid == other.uuid &&
|
||||
catName == other.catName &&
|
||||
prodId == other.prodId &&
|
||||
prodType == other.prodType &&
|
||||
name == other.name &&
|
||||
icon == other.icon;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
uuid.hashCode ^
|
||||
catName.hashCode ^
|
||||
prodId.hashCode ^
|
||||
prodType.hashCode ^
|
||||
name.hashCode ^
|
||||
icon.hashCode;
|
||||
}
|
||||
|
@ -1,22 +1,23 @@
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class Tag {
|
||||
String? uuid;
|
||||
String? tag;
|
||||
final ProductModel? product;
|
||||
String internalId;
|
||||
String? location;
|
||||
|
||||
Tag(
|
||||
{this.uuid,
|
||||
required this.tag,
|
||||
this.product,
|
||||
String? internalId,
|
||||
this.location})
|
||||
: internalId = internalId ?? const Uuid().v4();
|
||||
class Tag extends BaseTag {
|
||||
Tag({
|
||||
String? uuid,
|
||||
required String? tag,
|
||||
ProductModel? product,
|
||||
String? internalId,
|
||||
String? location,
|
||||
}) : super(
|
||||
uuid: uuid,
|
||||
tag: tag,
|
||||
product: product,
|
||||
internalId: internalId,
|
||||
location: location,
|
||||
);
|
||||
|
||||
factory Tag.fromJson(Map<String, dynamic> json) {
|
||||
final String internalId = json['internalId'] ?? const Uuid().v4();
|
||||
@ -31,15 +32,19 @@ class Tag {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Tag copyWith({
|
||||
String? tag,
|
||||
ProductModel? product,
|
||||
String? location,
|
||||
String? internalId,
|
||||
}) {
|
||||
return Tag(
|
||||
uuid: uuid,
|
||||
tag: tag ?? this.tag,
|
||||
product: product ?? this.product,
|
||||
location: location ?? this.location,
|
||||
internalId: internalId ?? this.internalId,
|
||||
);
|
||||
}
|
||||
|
||||
@ -60,7 +65,7 @@ extension TagModelExtensions on Tag {
|
||||
..productUuid = product?.uuid;
|
||||
}
|
||||
|
||||
CreateTagBodyModel toCreateTagBodyModel() {
|
||||
CreateTagBodyModel toCreateTagBodyModel() {
|
||||
return CreateTagBodyModel()
|
||||
..tag = tag ?? ''
|
||||
..productUuid = product?.uuid;
|
||||
|
@ -137,6 +137,7 @@ class _AddDeviceWidgetState extends State<AddDeviceWidget> {
|
||||
_buildDeviceName(product, size),
|
||||
const SizedBox(height: 4),
|
||||
CounterWidget(
|
||||
isCreate: false,
|
||||
initialCount: selectedProduct.count,
|
||||
onCountChanged: (newCount) {
|
||||
setState(() {
|
||||
|
@ -0,0 +1,68 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class CommunityStructureHeaderActionButtons extends StatelessWidget {
|
||||
const CommunityStructureHeaderActionButtons(
|
||||
{super.key,
|
||||
required this.theme,
|
||||
required this.isSave,
|
||||
required this.onSave,
|
||||
required this.onDelete,
|
||||
required this.selectedSpace,
|
||||
required this.onDuplicate,
|
||||
required this.onEdit});
|
||||
|
||||
final ThemeData theme;
|
||||
final bool isSave;
|
||||
final VoidCallback onSave;
|
||||
final VoidCallback onDelete;
|
||||
final VoidCallback onDuplicate;
|
||||
final VoidCallback onEdit;
|
||||
|
||||
final SpaceModel? selectedSpace;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final canShowActions = selectedSpace != null &&
|
||||
selectedSpace?.status != SpaceStatus.deleted &&
|
||||
selectedSpace?.status != SpaceStatus.parentDeleted;
|
||||
|
||||
return Wrap(
|
||||
alignment: WrapAlignment.end,
|
||||
spacing: 10,
|
||||
children: [
|
||||
if (isSave)
|
||||
CommunityStructureHeaderButton(
|
||||
label: "Save",
|
||||
icon: const Icon(Icons.save,
|
||||
size: 18, color: ColorsManager.spaceColor),
|
||||
onPressed: onSave,
|
||||
theme: theme,
|
||||
),
|
||||
if (canShowActions) ...[
|
||||
CommunityStructureHeaderButton(
|
||||
label: "Edit",
|
||||
svgAsset: Assets.editSpace,
|
||||
onPressed: onEdit,
|
||||
theme: theme,
|
||||
),
|
||||
CommunityStructureHeaderButton(
|
||||
label: "Duplicate",
|
||||
svgAsset: Assets.duplicate,
|
||||
onPressed: onDuplicate,
|
||||
theme: theme,
|
||||
),
|
||||
CommunityStructureHeaderButton(
|
||||
label: "Delete",
|
||||
svgAsset: Assets.spaceDelete,
|
||||
onPressed: onDelete,
|
||||
theme: theme,
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class CommunityStructureHeaderButton extends StatelessWidget {
|
||||
const CommunityStructureHeaderButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
this.icon,
|
||||
required this.onPressed,
|
||||
this.svgAsset,
|
||||
required this.theme,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final Widget? icon;
|
||||
final VoidCallback onPressed;
|
||||
final String? svgAsset;
|
||||
final ThemeData theme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const double buttonHeight = 40;
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 130,
|
||||
minHeight: buttonHeight,
|
||||
),
|
||||
child: DefaultButton(
|
||||
onPressed: onPressed,
|
||||
borderWidth: 2,
|
||||
backgroundColor: ColorsManager.textFieldGreyColor,
|
||||
foregroundColor: ColorsManager.blackColor,
|
||||
borderRadius: 12.0,
|
||||
padding: 2.0,
|
||||
height: buttonHeight,
|
||||
elevation: 0,
|
||||
borderColor: ColorsManager.lightGrayColor,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (icon != null) icon!,
|
||||
if (svgAsset != null)
|
||||
SvgPicture.asset(
|
||||
svgAsset!,
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
style: theme.textTheme.bodySmall
|
||||
?.copyWith(color: ColorsManager.blackColor, fontSize: 14),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_community/view/create_community_dialog.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
@ -14,6 +14,9 @@ class CommunityStructureHeader extends StatefulWidget {
|
||||
final TextEditingController nameController;
|
||||
final VoidCallback onSave;
|
||||
final VoidCallback onDelete;
|
||||
final VoidCallback onEdit;
|
||||
final VoidCallback onDuplicate;
|
||||
|
||||
final VoidCallback onEditName;
|
||||
final ValueChanged<String> onNameSubmitted;
|
||||
final List<CommunityModel> communities;
|
||||
@ -32,7 +35,9 @@ class CommunityStructureHeader extends StatefulWidget {
|
||||
required this.onNameSubmitted,
|
||||
this.community,
|
||||
required this.communities,
|
||||
this.selectedSpace});
|
||||
this.selectedSpace,
|
||||
required this.onDuplicate,
|
||||
required this.onEdit});
|
||||
|
||||
@override
|
||||
State<CommunityStructureHeader> createState() =>
|
||||
@ -141,70 +146,18 @@ class _CommunityStructureHeaderState extends State<CommunityStructureHeader> {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_buildActionButtons(theme),
|
||||
CommunityStructureHeaderActionButtons(
|
||||
theme: theme,
|
||||
isSave: widget.isSave,
|
||||
onSave: widget.onSave,
|
||||
onDelete: widget.onDelete,
|
||||
onDuplicate: widget.onDuplicate,
|
||||
onEdit: widget.onEdit,
|
||||
selectedSpace: widget.selectedSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActionButtons(ThemeData theme) {
|
||||
return Wrap(
|
||||
alignment: WrapAlignment.end,
|
||||
spacing: 10,
|
||||
children: [
|
||||
if (widget.isSave)
|
||||
_buildButton(
|
||||
label: "Save",
|
||||
icon: const Icon(Icons.save,
|
||||
size: 18, color: ColorsManager.spaceColor),
|
||||
onPressed: widget.onSave,
|
||||
theme: theme),
|
||||
if(widget.selectedSpace!= null)
|
||||
_buildButton(
|
||||
label: "Delete",
|
||||
icon: const Icon(Icons.delete,
|
||||
size: 18, color: ColorsManager.warningRed),
|
||||
onPressed: widget.onDelete,
|
||||
theme: theme),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildButton(
|
||||
{required String label,
|
||||
required Widget icon,
|
||||
required VoidCallback onPressed,
|
||||
required ThemeData theme}) {
|
||||
const double buttonHeight = 30;
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 80, minHeight: buttonHeight),
|
||||
child: DefaultButton(
|
||||
onPressed: onPressed,
|
||||
backgroundColor: ColorsManager.textFieldGreyColor,
|
||||
foregroundColor: ColorsManager.blackColor,
|
||||
borderRadius: 8.0,
|
||||
padding: 2.0,
|
||||
height: buttonHeight,
|
||||
elevation: 0,
|
||||
borderColor: ColorsManager.lightGrayColor,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
icon,
|
||||
const SizedBox(width: 5),
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
style: theme.textTheme.bodySmall
|
||||
?.copyWith(color: ColorsManager.blackColor),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
// Syncrow project imports
|
||||
import 'package:syncrow_web/pages/common/buttons/add_space_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/spaces_management/all_spaces/bloc/space_management_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||
@ -17,6 +19,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/blank_com
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/curved_line_painter.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_card_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_container_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||
@ -133,6 +136,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
onSave: _saveSpaces,
|
||||
selectedSpace: widget.selectedSpace,
|
||||
onDelete: _onDelete,
|
||||
onDuplicate: () => {_onDuplicate(context)},
|
||||
onEdit: () => {_showEditSpaceDialog()},
|
||||
onEditName: () {
|
||||
setState(() {
|
||||
isEditingName = !isEditingName;
|
||||
@ -177,7 +182,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
painter: CurvedLinePainter([connection])),
|
||||
),
|
||||
for (var entry in spaces.asMap().entries)
|
||||
if (entry.value.status != SpaceStatus.deleted ||
|
||||
if (entry.value.status != SpaceStatus.deleted &&
|
||||
entry.value.status != SpaceStatus.parentDeleted)
|
||||
Positioned(
|
||||
left: entry.value.position.dx,
|
||||
@ -209,9 +214,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
opacity: isHighlighted ? 1.0 : 0.3,
|
||||
child: SpaceContainerWidget(
|
||||
index: index,
|
||||
onDoubleTap: () {
|
||||
_showEditSpaceDialog(spaces[index]);
|
||||
},
|
||||
onTap: () {
|
||||
_selectSpace(context, spaces[index]);
|
||||
},
|
||||
@ -301,7 +303,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
List<Tag>? tags) {
|
||||
setState(() {
|
||||
// Set the first space in the center or use passed position
|
||||
|
||||
Offset centerPosition =
|
||||
position ?? _getCenterPosition(screenSize);
|
||||
SpaceModel newSpace = SpaceModel(
|
||||
@ -329,7 +330,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
parentSpace.addOutgoingConnection(newConnection);
|
||||
parentSpace.children.add(newSpace);
|
||||
}
|
||||
|
||||
spaces.add(newSpace);
|
||||
_updateNodePosition(newSpace, newSpace.position);
|
||||
});
|
||||
@ -339,39 +339,43 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
);
|
||||
}
|
||||
|
||||
void _showEditSpaceDialog(SpaceModel space) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return CreateSpaceDialog(
|
||||
products: widget.products,
|
||||
name: space.name,
|
||||
icon: space.icon,
|
||||
editSpace: space,
|
||||
isEdit: true,
|
||||
onCreateSpace: (String name,
|
||||
String icon,
|
||||
List<SelectedProduct> selectedProducts,
|
||||
SpaceTemplateModel? spaceModel,
|
||||
List<SubspaceModel>? subspaces,
|
||||
List<Tag>? tags) {
|
||||
setState(() {
|
||||
// Update the space's properties
|
||||
space.name = name;
|
||||
space.icon = icon;
|
||||
space.spaceModel = spaceModel;
|
||||
space.subspaces = subspaces;
|
||||
space.tags = tags;
|
||||
void _showEditSpaceDialog() {
|
||||
if (widget.selectedSpace != null) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return CreateSpaceDialog(
|
||||
products: widget.products,
|
||||
spaceModels: widget.spaceModels,
|
||||
name: widget.selectedSpace!.name,
|
||||
icon: widget.selectedSpace!.icon,
|
||||
editSpace: widget.selectedSpace,
|
||||
isEdit: true,
|
||||
onCreateSpace: (String name,
|
||||
String icon,
|
||||
List<SelectedProduct> selectedProducts,
|
||||
SpaceTemplateModel? spaceModel,
|
||||
List<SubspaceModel>? subspaces,
|
||||
List<Tag>? tags) {
|
||||
setState(() {
|
||||
// Update the space's properties
|
||||
widget.selectedSpace!.name = name;
|
||||
widget.selectedSpace!.icon = icon;
|
||||
widget.selectedSpace!.spaceModel = spaceModel;
|
||||
widget.selectedSpace!.subspaces = subspaces;
|
||||
widget.selectedSpace!.tags = tags;
|
||||
|
||||
if (space.status != SpaceStatus.newSpace) {
|
||||
space.status = SpaceStatus.modified; // Mark as modified
|
||||
}
|
||||
});
|
||||
},
|
||||
key: Key(space.name),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (widget.selectedSpace!.status != SpaceStatus.newSpace) {
|
||||
widget.selectedSpace!.status =
|
||||
SpaceStatus.modified; // Mark as modified
|
||||
}
|
||||
});
|
||||
},
|
||||
key: Key(widget.selectedSpace!.name),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleHoverChanged(int index, bool isHovered) {
|
||||
@ -385,10 +389,10 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
|
||||
void flatten(SpaceModel space) {
|
||||
if (space.status == SpaceStatus.deleted ||
|
||||
space.status == SpaceStatus.parentDeleted) return;
|
||||
|
||||
space.status == SpaceStatus.parentDeleted) {
|
||||
return;
|
||||
}
|
||||
result.add(space);
|
||||
|
||||
for (var child in space.children) {
|
||||
flatten(child);
|
||||
}
|
||||
@ -456,21 +460,15 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
}
|
||||
|
||||
void _onDelete() {
|
||||
if (widget.selectedCommunity != null &&
|
||||
widget.selectedCommunity?.uuid != null &&
|
||||
widget.selectedSpace == null) {
|
||||
context.read<SpaceManagementBloc>().add(DeleteCommunityEvent(
|
||||
communityUuid: widget.selectedCommunity!.uuid,
|
||||
));
|
||||
}
|
||||
if (widget.selectedSpace != null) {
|
||||
setState(() {
|
||||
for (var space in spaces) {
|
||||
if (space.uuid == widget.selectedSpace?.uuid) {
|
||||
if (space.internalId == widget.selectedSpace?.internalId) {
|
||||
space.status = SpaceStatus.deleted;
|
||||
_markChildrenAsDeleted(space);
|
||||
}
|
||||
}
|
||||
|
||||
_removeConnectionsForDeletedSpaces();
|
||||
});
|
||||
}
|
||||
@ -552,4 +550,196 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
||||
space.status == SpaceStatus.modified ||
|
||||
space.status == SpaceStatus.deleted);
|
||||
}
|
||||
|
||||
void _onDuplicate(BuildContext parentContext) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
if (widget.selectedSpace != null) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
title: Text(
|
||||
"Duplicate Space",
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineLarge
|
||||
?.copyWith(color: ColorsManager.blackColor),
|
||||
),
|
||||
content: SizedBox(
|
||||
width: screenWidth * 0.4,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text("Are you sure you want to duplicate?",
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.headlineSmall),
|
||||
const SizedBox(height: 15),
|
||||
Text("All the child spaces will be duplicated.",
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(color: ColorsManager.lightGrayColor)),
|
||||
const SizedBox(width: 15),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: CancelButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
label: "Cancel",
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: DefaultButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return DuplicateProcessDialog(
|
||||
onDuplicate: () {
|
||||
_duplicateSpace(widget.selectedSpace!);
|
||||
_deselectSpace(parentContext);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
borderRadius: 10,
|
||||
foregroundColor: ColorsManager.whiteColors,
|
||||
child: const Text('OK'),
|
||||
),
|
||||
),
|
||||
])
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _duplicateSpace(SpaceModel space) {
|
||||
final Map<SpaceModel, SpaceModel> originalToDuplicate = {};
|
||||
const double horizontalGap = 200.0;
|
||||
const double verticalGap = 100.0;
|
||||
|
||||
final Map<String, int> nameCounters = {};
|
||||
|
||||
String _generateCopyName(String originalName) {
|
||||
final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim();
|
||||
nameCounters[baseName] = (nameCounters[baseName] ?? 0) + 1;
|
||||
return "$baseName(${nameCounters[baseName]})";
|
||||
}
|
||||
|
||||
SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition,
|
||||
SpaceModel? duplicatedParent) {
|
||||
Offset newPosition = parentPosition + Offset(horizontalGap, 0);
|
||||
|
||||
while (spaces.any((s) =>
|
||||
(s.position - newPosition).distance < horizontalGap &&
|
||||
s.status != SpaceStatus.deleted)) {
|
||||
newPosition += Offset(horizontalGap, 0);
|
||||
}
|
||||
|
||||
final duplicatedName = _generateCopyName(original.name);
|
||||
|
||||
final duplicated = SpaceModel(
|
||||
name: duplicatedName,
|
||||
icon: original.icon,
|
||||
position: newPosition,
|
||||
isPrivate: original.isPrivate,
|
||||
children: [],
|
||||
status: SpaceStatus.newSpace,
|
||||
parent: duplicatedParent,
|
||||
spaceModel: original.spaceModel,
|
||||
subspaces: original.subspaces,
|
||||
tags: original.tags,
|
||||
);
|
||||
|
||||
originalToDuplicate[original] = duplicated;
|
||||
|
||||
setState(() {
|
||||
spaces.add(duplicated);
|
||||
_updateNodePosition(duplicated, duplicated.position);
|
||||
|
||||
if (duplicatedParent != null) {
|
||||
final newConnection = Connection(
|
||||
startSpace: duplicatedParent,
|
||||
endSpace: duplicated,
|
||||
direction: "down",
|
||||
);
|
||||
connections.add(newConnection);
|
||||
duplicated.incomingConnection = newConnection;
|
||||
duplicatedParent.addOutgoingConnection(newConnection);
|
||||
}
|
||||
|
||||
if (original.parent != null && duplicatedParent == null) {
|
||||
final originalParent = original.parent!;
|
||||
final duplicatedParent =
|
||||
originalToDuplicate[originalParent] ?? originalParent;
|
||||
|
||||
final parentConnection = Connection(
|
||||
startSpace: duplicatedParent,
|
||||
endSpace: duplicated,
|
||||
direction: original.incomingConnection?.direction ?? "down",
|
||||
);
|
||||
|
||||
connections.add(parentConnection);
|
||||
duplicated.incomingConnection = parentConnection;
|
||||
duplicatedParent.addOutgoingConnection(parentConnection);
|
||||
}
|
||||
});
|
||||
|
||||
final childrenWithDownDirection = original.children
|
||||
.where((child) =>
|
||||
child.incomingConnection?.direction == "down" &&
|
||||
child.status != SpaceStatus.deleted)
|
||||
.toList();
|
||||
|
||||
Offset childStartPosition = childrenWithDownDirection.length == 1
|
||||
? duplicated.position
|
||||
: newPosition + Offset(0, verticalGap);
|
||||
|
||||
for (final child in original.children) {
|
||||
final isDownDirection =
|
||||
child.incomingConnection?.direction == "down" ?? false;
|
||||
|
||||
if (isDownDirection && childrenWithDownDirection.length == 1) {
|
||||
// Place the only "down" child vertically aligned with the parent
|
||||
childStartPosition = duplicated.position + Offset(0, verticalGap);
|
||||
} else if (!isDownDirection) {
|
||||
// Position children with other directions horizontally
|
||||
childStartPosition = duplicated.position + Offset(horizontalGap, 0);
|
||||
}
|
||||
|
||||
final duplicatedChild =
|
||||
duplicateRecursive(child, childStartPosition, duplicated);
|
||||
duplicated.children.add(duplicatedChild);
|
||||
childStartPosition += Offset(0, verticalGap);
|
||||
}
|
||||
|
||||
return duplicated;
|
||||
}
|
||||
|
||||
if (space.parent == null) {
|
||||
duplicateRecursive(space, space.position, null);
|
||||
} else {
|
||||
final duplicatedParent =
|
||||
originalToDuplicate[space.parent!] ?? space.parent!;
|
||||
duplicateRecursive(space, space.position, duplicatedParent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,14 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
||||
class CounterWidget extends StatefulWidget {
|
||||
final int initialCount;
|
||||
final ValueChanged<int> onCountChanged;
|
||||
final bool isCreate;
|
||||
|
||||
const CounterWidget({
|
||||
Key? key,
|
||||
this.initialCount = 0,
|
||||
required this.onCountChanged,
|
||||
}) : super(key: key);
|
||||
const CounterWidget(
|
||||
{Key? key,
|
||||
this.initialCount = 0,
|
||||
required this.onCountChanged,
|
||||
required this.isCreate})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<CounterWidget> createState() => _CounterWidgetState();
|
||||
@ -53,25 +55,26 @@ class _CounterWidgetState extends State<CounterWidget> {
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildCounterButton(Icons.remove, _decrementCounter),
|
||||
_buildCounterButton(Icons.remove, _decrementCounter,!widget.isCreate ),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'$_counter',
|
||||
style: theme.textTheme.bodyLarge?.copyWith(color: ColorsManager.spaceColor),
|
||||
style: theme.textTheme.bodyLarge
|
||||
?.copyWith(color: ColorsManager.spaceColor),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_buildCounterButton(Icons.add, _incrementCounter),
|
||||
_buildCounterButton(Icons.add, _incrementCounter, false),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCounterButton(IconData icon, VoidCallback onPressed) {
|
||||
Widget _buildCounterButton(IconData icon, VoidCallback onPressed, bool isDisabled) {
|
||||
return GestureDetector(
|
||||
onTap: onPressed,
|
||||
onTap: isDisabled? null: onPressed,
|
||||
child: Icon(
|
||||
icon,
|
||||
color: ColorsManager.spaceColor,
|
||||
color: isDisabled? ColorsManager.spaceColor.withOpacity(0.3): ColorsManager.spaceColor,
|
||||
size: 18,
|
||||
),
|
||||
);
|
||||
|
@ -1,5 +1,6 @@
|
||||
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/default_button.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_widget.dart';
|
||||
@ -9,16 +10,25 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/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/create_subspace/views/create_subspace_model_dialog.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/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/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/constants/space_icon_const.dart';
|
||||
|
||||
class CreateSpaceDialog extends StatefulWidget {
|
||||
final Function(String, String, List<SelectedProduct> selectedProducts,
|
||||
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) onCreateSpace;
|
||||
final Function(
|
||||
String,
|
||||
String,
|
||||
List<SelectedProduct> selectedProducts,
|
||||
SpaceTemplateModel? spaceModel,
|
||||
List<SubspaceModel>? subspaces,
|
||||
List<Tag>? tags) onCreateSpace;
|
||||
final List<ProductModel>? products;
|
||||
final String? name;
|
||||
final String? icon;
|
||||
@ -139,50 +149,53 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextField(
|
||||
controller: nameController,
|
||||
onChanged: (value) {
|
||||
enteredName = value.trim();
|
||||
setState(() {
|
||||
isNameFieldExist = false;
|
||||
isOkButtonEnabled = false;
|
||||
isNameFieldInvalid = value.isEmpty;
|
||||
SizedBox(
|
||||
width: screenWidth * 0.25,
|
||||
child: TextField(
|
||||
controller: nameController,
|
||||
onChanged: (value) {
|
||||
enteredName = value.trim();
|
||||
setState(() {
|
||||
isNameFieldExist = false;
|
||||
isOkButtonEnabled = false;
|
||||
isNameFieldInvalid = value.isEmpty;
|
||||
|
||||
if (!isNameFieldInvalid) {
|
||||
if (_isNameConflict(value)) {
|
||||
isNameFieldExist = true;
|
||||
isOkButtonEnabled = false;
|
||||
} else {
|
||||
isNameFieldExist = false;
|
||||
isOkButtonEnabled = true;
|
||||
if (!isNameFieldInvalid) {
|
||||
if (_isNameConflict(value)) {
|
||||
isNameFieldExist = true;
|
||||
isOkButtonEnabled = false;
|
||||
} else {
|
||||
isNameFieldExist = false;
|
||||
isOkButtonEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
style: const TextStyle(color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Please enter the name',
|
||||
hintStyle: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: ColorsManager.lightGrayColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: ColorsManager.boxColor,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: BorderSide(
|
||||
color: isNameFieldInvalid || isNameFieldExist
|
||||
? ColorsManager.red
|
||||
: ColorsManager.boxColor,
|
||||
width: 1.5,
|
||||
});
|
||||
},
|
||||
style: const TextStyle(color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Please enter the name',
|
||||
hintStyle: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: ColorsManager.lightGrayColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: const BorderSide(
|
||||
color: ColorsManager.boxColor,
|
||||
width: 1.5,
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -211,42 +224,16 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
selectedSpaceModel == null
|
||||
? DefaultButton(
|
||||
? TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
onPressed: () {
|
||||
_showLinkSpaceModelDialog(context);
|
||||
},
|
||||
backgroundColor: ColorsManager.textFieldGreyColor,
|
||||
foregroundColor: Colors.black,
|
||||
borderColor: ColorsManager.neutralGray,
|
||||
borderRadius: 16.0,
|
||||
padding: 10.0, // Reduced padding for smaller size
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 6.0),
|
||||
child: SvgPicture.asset(
|
||||
Assets.link,
|
||||
width: screenWidth *
|
||||
0.015, // Adjust icon size
|
||||
height: screenWidth * 0.015,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 3),
|
||||
Flexible(
|
||||
child: Text(
|
||||
'Link a space model',
|
||||
overflow: TextOverflow
|
||||
.ellipsis, // Prevent overflow
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const ButtonContentWidget(
|
||||
svgAssets: Assets.link,
|
||||
label: 'Link a space model',
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
@ -307,7 +294,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
||||
padding: EdgeInsets.symmetric(horizontal: 6.0),
|
||||
child: Text(
|
||||
'OR',
|
||||
style: TextStyle(
|
||||
@ -326,47 +313,22 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
),
|
||||
const SizedBox(height: 25),
|
||||
subspaces == null
|
||||
? DefaultButton(
|
||||
onPressed: () {
|
||||
? TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
overlayColor: ColorsManager.transparentColor,
|
||||
),
|
||||
onPressed: () async {
|
||||
_showSubSpaceDialog(context, enteredName, [],
|
||||
false, widget.products, subspaces);
|
||||
},
|
||||
backgroundColor: ColorsManager.textFieldGreyColor,
|
||||
foregroundColor: Colors.black,
|
||||
borderColor: ColorsManager.neutralGray,
|
||||
borderRadius: 16.0,
|
||||
padding: 10.0, // Reduced padding for smaller size
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 6.0),
|
||||
child: SvgPicture.asset(
|
||||
Assets.addIcon,
|
||||
width: screenWidth *
|
||||
0.015, // Adjust icon size
|
||||
height: screenWidth * 0.015,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 3),
|
||||
Flexible(
|
||||
child: Text(
|
||||
'Create sub space',
|
||||
overflow: TextOverflow
|
||||
.ellipsis, // Prevent overflow
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const ButtonContentWidget(
|
||||
icon: Icons.add,
|
||||
label: 'Create Sub Space',
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
width: screenWidth * 0.35,
|
||||
width: screenWidth * 0.25,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(
|
||||
@ -382,50 +344,27 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
if (subspaces != null)
|
||||
...subspaces!.map(
|
||||
(subspace) => Chip(
|
||||
label: Text(
|
||||
subspace.subspaceName,
|
||||
style: const TextStyle(
|
||||
color: ColorsManager
|
||||
.spaceColor), // Text color
|
||||
),
|
||||
backgroundColor: ColorsManager
|
||||
.whiteColors, // Chip background color
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
16), // Rounded chip
|
||||
side: const BorderSide(
|
||||
color: ColorsManager
|
||||
.spaceColor), // Border color
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
...subspaces!.map((subspace) =>
|
||||
SubspaceNameDisplayWidget(
|
||||
validateName: (updatedName) {
|
||||
return !subspaces!.any((s) =>
|
||||
s != subspace &&
|
||||
s.subspaceName == updatedName);
|
||||
},
|
||||
text: subspace.subspaceName,
|
||||
onNameChanged: (updatedName) {
|
||||
setState(() {
|
||||
subspace.subspaceName =
|
||||
updatedName;
|
||||
});
|
||||
},
|
||||
)),
|
||||
EditChip(
|
||||
onTap: () async {
|
||||
_showSubSpaceDialog(
|
||||
context,
|
||||
enteredName,
|
||||
[],
|
||||
false,
|
||||
widget.products,
|
||||
subspaces);
|
||||
_showSubSpaceDialog(context, enteredName,
|
||||
[], true, widget.products, subspaces);
|
||||
},
|
||||
child: Chip(
|
||||
label: const Text(
|
||||
'Edit',
|
||||
style: TextStyle(
|
||||
color: ColorsManager.spaceColor),
|
||||
),
|
||||
backgroundColor:
|
||||
ColorsManager.whiteColors,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: const BorderSide(
|
||||
color: ColorsManager.spaceColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -452,7 +391,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
// Combine tags from spaceModel and subspaces
|
||||
..._groupTags([
|
||||
...TagHelper.groupTags([
|
||||
...?tags,
|
||||
...?subspaces?.expand(
|
||||
(subspace) => subspace.tags ?? [])
|
||||
@ -484,70 +423,38 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
_showTagCreateDialog(context, enteredName,
|
||||
widget.products);
|
||||
// Edit action
|
||||
},
|
||||
child: Chip(
|
||||
label: const Text(
|
||||
'Edit',
|
||||
style: TextStyle(
|
||||
color: ColorsManager.spaceColor),
|
||||
),
|
||||
backgroundColor:
|
||||
ColorsManager.whiteColors,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: const BorderSide(
|
||||
color: ColorsManager.spaceColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
EditChip(onTap: () async {
|
||||
_showTagCreateDialog(
|
||||
context,
|
||||
enteredName,
|
||||
widget.isEdit,
|
||||
widget.products,
|
||||
subspaces,
|
||||
);
|
||||
// Edit action
|
||||
})
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: DefaultButton(
|
||||
: TextButton(
|
||||
onPressed: () {
|
||||
_showTagCreateDialog(
|
||||
context, enteredName, widget.products);
|
||||
context,
|
||||
enteredName,
|
||||
widget.isEdit,
|
||||
widget.products,
|
||||
subspaces,
|
||||
);
|
||||
},
|
||||
backgroundColor: ColorsManager.textFieldGreyColor,
|
||||
foregroundColor: Colors.black,
|
||||
borderColor: ColorsManager.neutralGray,
|
||||
borderRadius: 16.0,
|
||||
padding: 10.0, // Reduced padding for smaller size
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 6.0),
|
||||
child: SvgPicture.asset(
|
||||
Assets.addIcon,
|
||||
width: screenWidth *
|
||||
0.015, // Adjust icon size
|
||||
height: screenWidth * 0.015,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 3),
|
||||
Flexible(
|
||||
child: Text(
|
||||
'Add devices',
|
||||
overflow: TextOverflow
|
||||
.ellipsis, // Prevent overflow
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
)
|
||||
child: const ButtonContentWidget(
|
||||
icon: Icons.add,
|
||||
label: 'Add Devices',
|
||||
))
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -579,8 +486,13 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
? enteredName
|
||||
: (widget.name ?? '');
|
||||
if (newName.isNotEmpty) {
|
||||
widget.onCreateSpace(newName, selectedIcon,
|
||||
selectedProducts, selectedSpaceModel,subspaces,tags);
|
||||
widget.onCreateSpace(
|
||||
newName,
|
||||
selectedIcon,
|
||||
selectedProducts,
|
||||
selectedSpaceModel,
|
||||
subspaces,
|
||||
tags);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
@ -655,7 +567,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
builder: (BuildContext context) {
|
||||
return CreateSubSpaceDialog(
|
||||
spaceName: name,
|
||||
dialogTitle: 'Create Sub-space',
|
||||
dialogTitle: isEdit ? 'Edit Sub-space' : 'Create Sub-space',
|
||||
spaceTags: spaceTags,
|
||||
isEdit: isEdit,
|
||||
products: products,
|
||||
@ -672,85 +584,57 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||
);
|
||||
}
|
||||
|
||||
void _showTagCreateDialog(
|
||||
BuildContext context, String name, List<ProductModel>? products) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AddDeviceTypeWidget(
|
||||
spaceName: name,
|
||||
products: products,
|
||||
subspaces: subspaces,
|
||||
spaceTags: tags,
|
||||
allTags: [],
|
||||
initialSelectedProducts:
|
||||
createInitialSelectedProducts(tags, subspaces),
|
||||
onSave: (selectedSpaceTags, selectedSubspaces) {
|
||||
setState(() {
|
||||
tags = selectedSpaceTags;
|
||||
selectedSpaceModel = null;
|
||||
void _showTagCreateDialog(BuildContext context, String name, bool isEdit,
|
||||
List<ProductModel>? products, List<SubspaceModel>? subspaces) {
|
||||
isEdit
|
||||
? showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AssignTagDialog(
|
||||
title: 'Edit Device',
|
||||
addedProducts: TagHelper.createInitialSelectedProductsForTags(
|
||||
tags, subspaces),
|
||||
spaceName: name,
|
||||
products: products,
|
||||
subspaces: subspaces,
|
||||
allTags: [],
|
||||
onSave: (selectedSpaceTags, selectedSubspaces) {},
|
||||
);
|
||||
},
|
||||
)
|
||||
: showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AddDeviceTypeWidget(
|
||||
spaceName: name,
|
||||
products: products,
|
||||
subspaces: subspaces,
|
||||
spaceTags: tags,
|
||||
allTags: [],
|
||||
initialSelectedProducts:
|
||||
TagHelper.createInitialSelectedProductsForTags(
|
||||
tags, subspaces),
|
||||
onSave: (selectedSpaceTags, selectedSubspaces) {
|
||||
setState(() {
|
||||
tags = selectedSpaceTags;
|
||||
selectedSpaceModel = null;
|
||||
|
||||
if (selectedSubspaces != null) {
|
||||
if (subspaces != null) {
|
||||
for (final subspace in subspaces!) {
|
||||
for (final selectedSubspace in selectedSubspaces) {
|
||||
if (subspace.subspaceName ==
|
||||
selectedSubspace.subspaceName) {
|
||||
subspace.tags = selectedSubspace.tags;
|
||||
if (selectedSubspaces != null) {
|
||||
if (subspaces != null) {
|
||||
for (final subspace in subspaces!) {
|
||||
for (final selectedSubspace in selectedSubspaces) {
|
||||
if (subspace.subspaceName ==
|
||||
selectedSubspace.subspaceName) {
|
||||
subspace.tags = selectedSubspace.tags;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<SelectedProduct> createInitialSelectedProducts(
|
||||
List<Tag>? tags, List<SubspaceModel>? subspaces) {
|
||||
final Map<ProductModel, int> productCounts = {};
|
||||
|
||||
if (tags != null) {
|
||||
for (var tag in tags) {
|
||||
if (tag.product != null) {
|
||||
productCounts[tag.product!] = (productCounts[tag.product!] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (subspaces != null) {
|
||||
for (var subspace in subspaces) {
|
||||
if (subspace.tags != null) {
|
||||
for (var tag in subspace.tags!) {
|
||||
if (tag.product != null) {
|
||||
productCounts[tag.product!] =
|
||||
(productCounts[tag.product!] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return productCounts.entries
|
||||
.map((entry) => SelectedProduct(
|
||||
productId: entry.key.uuid,
|
||||
count: entry.value,
|
||||
productName: entry.key.name ?? 'Unnamed',
|
||||
product: entry.key,
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Map<ProductModel, int> _groupTags(List<Tag> tags) {
|
||||
final Map<ProductModel, int> groupedTags = {};
|
||||
for (var tag in tags) {
|
||||
if (tag.product != null) {
|
||||
groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
return groupedTags;
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class DuplicateProcessDialog extends StatefulWidget {
|
||||
final VoidCallback onDuplicate;
|
||||
|
||||
const DuplicateProcessDialog({required this.onDuplicate, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_DuplicateProcessDialogState createState() => _DuplicateProcessDialogState();
|
||||
}
|
||||
|
||||
class _DuplicateProcessDialogState extends State<DuplicateProcessDialog> {
|
||||
bool isDuplicating = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_startDuplication();
|
||||
}
|
||||
|
||||
void _startDuplication() async {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
widget.onDuplicate();
|
||||
});
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
setState(() {
|
||||
isDuplicating = false;
|
||||
});
|
||||
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
return AlertDialog(
|
||||
backgroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
content: SizedBox(
|
||||
width: screenWidth * 0.4,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (isDuplicating) ...[
|
||||
const CircularProgressIndicator(
|
||||
color: ColorsManager.vividBlue,
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
Text(
|
||||
"Duplicating in progress",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall
|
||||
?.copyWith(color: ColorsManager.primaryColor),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
] else ...[
|
||||
const Icon(
|
||||
Icons.check_circle,
|
||||
color: ColorsManager.vividBlue,
|
||||
size: 50,
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
Text(
|
||||
"Duplicating successful",
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall
|
||||
?.copyWith(color: ColorsManager.primaryColor),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -199,9 +199,11 @@ class _SidebarWidgetState extends State<SidebarWidget> {
|
||||
},
|
||||
children: hasChildren
|
||||
? community.spaces
|
||||
.where((space) => (space.status != SpaceStatus.deleted ||
|
||||
space.status != SpaceStatus.parentDeleted))
|
||||
.map((space) => _buildSpaceTile(space, community))
|
||||
.toList()
|
||||
: null, // Render spaces within the community
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -46,9 +46,9 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
||||
}
|
||||
|
||||
emit(AssignTagLoaded(
|
||||
tags: allTags,
|
||||
isSaveEnabled: _validateTags(allTags),
|
||||
));
|
||||
tags: allTags,
|
||||
isSaveEnabled: _validateTags(allTags),
|
||||
errorMessage: ''));
|
||||
});
|
||||
|
||||
on<UpdateTagEvent>((event, emit) {
|
||||
@ -117,12 +117,10 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
||||
}
|
||||
|
||||
bool _validateTags(List<Tag> tags) {
|
||||
if (tags.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet();
|
||||
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
|
||||
return uniqueTags.length == tags.length && !hasEmptyTag;
|
||||
final isValid = uniqueTags.length == tags.length && !hasEmptyTag;
|
||||
return isValid;
|
||||
}
|
||||
|
||||
String? _getValidationError(List<Tag> tags) {
|
||||
|
@ -1,7 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/common/dialog_dropdown.dart';
|
||||
import 'package:syncrow_web/common/dialog_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/selected_product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||
@ -79,6 +82,7 @@ class AssignTagDialog extends StatelessWidget {
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium)),
|
||||
DataColumn(
|
||||
numeric: false,
|
||||
label: Text('Tag',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium)),
|
||||
@ -109,10 +113,11 @@ class AssignTagDialog extends StatelessWidget {
|
||||
: List.generate(state.tags.length, (index) {
|
||||
final tag = state.tags[index];
|
||||
final controller = controllers[index];
|
||||
|
||||
final availableTags = getAvailableTags(
|
||||
allTags ?? [], state.tags, tag);
|
||||
return DataRow(
|
||||
cells: [
|
||||
DataCell(Text(index.toString())),
|
||||
DataCell(Text((index + 1).toString())),
|
||||
DataCell(
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
@ -123,147 +128,80 @@ class AssignTagDialog extends StatelessWidget {
|
||||
tag.product?.name ?? 'Unknown',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close,
|
||||
color: ColorsManager.warningRed,
|
||||
size: 16),
|
||||
onPressed: () {
|
||||
context.read<AssignTagBloc>().add(
|
||||
DeleteTag(
|
||||
tagToDelete: tag,
|
||||
tags: state.tags));
|
||||
},
|
||||
tooltip: 'Delete Tag',
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: controller,
|
||||
onChanged: (value) {
|
||||
context
|
||||
.read<AssignTagBloc>()
|
||||
.add(UpdateTagEvent(
|
||||
index: index,
|
||||
tag: value.trim(),
|
||||
));
|
||||
},
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Enter Tag',
|
||||
border: InputBorder.none,
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: ColorsManager.blackColor,
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context)
|
||||
.size
|
||||
.width *
|
||||
0.15,
|
||||
child: PopupMenuButton<String>(
|
||||
color: ColorsManager.whiteColors,
|
||||
child: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.arrow_drop_down,
|
||||
color:
|
||||
ColorsManager.blackColor),
|
||||
onSelected: (value) {
|
||||
controller.text = value;
|
||||
Icons.close,
|
||||
color: ColorsManager
|
||||
.lightGreyColor,
|
||||
size: 16,
|
||||
),
|
||||
onPressed: () {
|
||||
context
|
||||
.read<AssignTagBloc>()
|
||||
.add(UpdateTagEvent(
|
||||
index: index,
|
||||
tag: value,
|
||||
));
|
||||
},
|
||||
itemBuilder: (context) {
|
||||
return (allTags ?? [])
|
||||
.where((tagValue) => !state
|
||||
.tags
|
||||
.map((e) => e.tag)
|
||||
.contains(tagValue))
|
||||
.map((tagValue) {
|
||||
return PopupMenuItem<String>(
|
||||
textStyle: const TextStyle(
|
||||
color: ColorsManager
|
||||
.textPrimaryColor),
|
||||
value: tagValue,
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
BoxConstraints(
|
||||
minWidth: MediaQuery.of(
|
||||
context)
|
||||
.size
|
||||
.width *
|
||||
0.15,
|
||||
maxWidth: MediaQuery.of(
|
||||
context)
|
||||
.size
|
||||
.width *
|
||||
0.15,
|
||||
),
|
||||
child: Text(
|
||||
tagValue,
|
||||
overflow: TextOverflow
|
||||
.ellipsis,
|
||||
),
|
||||
));
|
||||
}).toList();
|
||||
.add(DeleteTag(
|
||||
tagToDelete: tag,
|
||||
tags: state.tags));
|
||||
},
|
||||
tooltip: 'Delete Tag',
|
||||
padding: EdgeInsets.zero,
|
||||
constraints:
|
||||
const BoxConstraints(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton<String>(
|
||||
value: tag.location ?? 'Main',
|
||||
dropdownColor: ColorsManager
|
||||
.whiteColors, // Dropdown background
|
||||
style: const TextStyle(
|
||||
color: Colors
|
||||
.black), // Style for selected text
|
||||
items: [
|
||||
const DropdownMenuItem<String>(
|
||||
value: 'Main Space',
|
||||
child: Text(
|
||||
'Main Space',
|
||||
style: TextStyle(
|
||||
color: ColorsManager
|
||||
.textPrimaryColor),
|
||||
),
|
||||
),
|
||||
...locations.map((location) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: location,
|
||||
child: Text(
|
||||
location,
|
||||
style: const TextStyle(
|
||||
color: ColorsManager
|
||||
.textPrimaryColor),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
Container(
|
||||
alignment: Alignment
|
||||
.centerLeft, // Align cell content to the left
|
||||
child: SizedBox(
|
||||
width: double
|
||||
.infinity, // Ensure full width for dropdown
|
||||
child: DialogTextfieldDropdown(
|
||||
items: availableTags,
|
||||
initialValue: tag.tag,
|
||||
onSelected: (value) {
|
||||
controller.text = value;
|
||||
context
|
||||
.read<AssignTagBloc>()
|
||||
.add(UpdateTagEvent(
|
||||
index: index,
|
||||
tag: value.trim(),
|
||||
));
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
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,
|
||||
));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
)),
|
||||
),
|
||||
],
|
||||
);
|
||||
@ -284,11 +222,33 @@ class AssignTagDialog extends StatelessWidget {
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: CancelButton(
|
||||
label: 'Cancel',
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Builder(
|
||||
builder: (buttonContext) => CancelButton(
|
||||
label: 'Add New Device',
|
||||
onPressed: () async {
|
||||
final updatedTags = List<Tag>.from(state.tags);
|
||||
final result = processTags(updatedTags, subspaces);
|
||||
|
||||
final processedTags =
|
||||
result['updatedTags'] as List<Tag>;
|
||||
final processedSubspaces = result['subspaces'];
|
||||
|
||||
Navigator.of(context).pop();
|
||||
|
||||
await showDialog<bool>(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (dialogContext) => AddDeviceTypeWidget(
|
||||
products: products,
|
||||
subspaces: processedSubspaces,
|
||||
initialSelectedProducts: addedProducts,
|
||||
allTags: allTags,
|
||||
spaceName: spaceName,
|
||||
spaceTags: processedTags,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
@ -302,22 +262,16 @@ class AssignTagDialog extends StatelessWidget {
|
||||
onPressed: state.isSaveEnabled
|
||||
? () async {
|
||||
Navigator.of(context).pop();
|
||||
final assignedTags = <Tag>{};
|
||||
for (var tag in state.tags) {
|
||||
if (tag.location == null ||
|
||||
subspaces == null) {
|
||||
continue;
|
||||
}
|
||||
for (var subspace in subspaces!) {
|
||||
if (tag.location == subspace.subspaceName) {
|
||||
subspace.tags ??= [];
|
||||
subspace.tags!.add(tag);
|
||||
assignedTags.add(tag);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
onSave!(state.tags,subspaces);
|
||||
final updatedTags = List<Tag>.from(state.tags);
|
||||
final result =
|
||||
processTags(updatedTags, subspaces);
|
||||
|
||||
final processedTags =
|
||||
result['updatedTags'] as List<Tag>;
|
||||
final processedSubspaces =
|
||||
result['subspaces'] as List<SubspaceModel>;
|
||||
|
||||
onSave!(processedTags, processedSubspaces);
|
||||
}
|
||||
: null,
|
||||
child: const Text('Save'),
|
||||
@ -337,4 +291,110 @@ class AssignTagDialog extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<String> getAvailableTags(
|
||||
List<String> allTags, List<Tag> currentTags, Tag currentTag) {
|
||||
return allTags
|
||||
.where((tagValue) => !currentTags
|
||||
.where((e) => e != currentTag) // Exclude the current row
|
||||
.map((e) => e.tag)
|
||||
.contains(tagValue))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Map<String, dynamic> processTags(
|
||||
List<Tag> updatedTags, List<SubspaceModel>? subspaces) {
|
||||
final modifiedTags = List<Tag>.from(updatedTags);
|
||||
final modifiedSubspaces = List<SubspaceModel>.from(subspaces ?? []);
|
||||
|
||||
for (var tag in modifiedTags.toList()) {
|
||||
if (modifiedSubspaces.isEmpty) continue;
|
||||
|
||||
final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces);
|
||||
|
||||
if ((tag.location == 'Main Space' || tag.location == null) &&
|
||||
(prevIndice == null ||
|
||||
modifiedSubspaces[prevIndice].subspaceName == 'Main Space')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((tag.location == 'Main Space' || tag.location == null) &&
|
||||
prevIndice != null) {
|
||||
modifiedSubspaces[prevIndice]
|
||||
.tags
|
||||
?.removeWhere((t) => t.internalId == tag.internalId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((tag.location != 'Main Space' && tag.location != null) &&
|
||||
prevIndice == null) {
|
||||
final newIndex = modifiedSubspaces
|
||||
.indexWhere((subspace) => subspace.subspaceName == tag.location);
|
||||
if (newIndex != -1) {
|
||||
if (modifiedSubspaces[newIndex]
|
||||
.tags
|
||||
?.any((t) => t.internalId == tag.internalId) !=
|
||||
true) {
|
||||
tag.location = modifiedSubspaces[newIndex].subspaceName;
|
||||
modifiedSubspaces[newIndex].tags?.add(tag);
|
||||
}
|
||||
}
|
||||
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((tag.location != 'Main Space' && tag.location != null) &&
|
||||
tag.location != modifiedSubspaces[prevIndice!].subspaceName) {
|
||||
modifiedSubspaces[prevIndice]
|
||||
.tags
|
||||
?.removeWhere((t) => t.internalId == tag.internalId);
|
||||
final newIndex = modifiedSubspaces
|
||||
.indexWhere((subspace) => subspace.subspaceName == tag.location);
|
||||
if (newIndex != -1) {
|
||||
if (modifiedSubspaces[newIndex]
|
||||
.tags
|
||||
?.any((t) => t.internalId == tag.internalId) !=
|
||||
true) {
|
||||
tag.location = modifiedSubspaces[newIndex].subspaceName;
|
||||
modifiedSubspaces[newIndex].tags?.add(tag);
|
||||
}
|
||||
}
|
||||
|
||||
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((tag.location != 'Main Space' && tag.location != null) &&
|
||||
tag.location == modifiedSubspaces[prevIndice!].subspaceName) {
|
||||
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((tag.location == 'Main Space' || tag.location == null) &&
|
||||
prevIndice != null) {
|
||||
modifiedSubspaces[prevIndice]
|
||||
.tags
|
||||
?.removeWhere((t) => t.internalId == tag.internalId);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
'updatedTags': modifiedTags,
|
||||
'subspaces': modifiedSubspaces,
|
||||
};
|
||||
}
|
||||
|
||||
int? checkTagExistInSubspace(Tag tag, List<SubspaceModel>? subspaces) {
|
||||
if (subspaces == null) return null;
|
||||
for (int i = 0; i < subspaces.length; i++) {
|
||||
final subspace = subspaces[i];
|
||||
if (subspace.tags == null) continue;
|
||||
for (var t in subspace.tags!) {
|
||||
if (tag.internalId == t.internalId) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -47,14 +47,13 @@ class AssignTagModelBloc
|
||||
}
|
||||
|
||||
emit(AssignTagModelLoaded(
|
||||
tags: allTags,
|
||||
isSaveEnabled: _validateTags(allTags),
|
||||
));
|
||||
tags: allTags,
|
||||
isSaveEnabled: _validateTags(allTags),
|
||||
errorMessage: ''));
|
||||
});
|
||||
|
||||
on<UpdateTag>((event, emit) {
|
||||
final currentState = state;
|
||||
|
||||
if (currentState is AssignTagModelLoaded &&
|
||||
currentState.tags.isNotEmpty) {
|
||||
final tags = List<TagModel>.from(currentState.tags);
|
||||
@ -122,9 +121,7 @@ class AssignTagModelBloc
|
||||
}
|
||||
|
||||
bool _validateTags(List<TagModel> tags) {
|
||||
if (tags.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet();
|
||||
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
|
||||
final isValid = uniqueTags.length == tags.length && !hasEmptyTag;
|
||||
@ -133,7 +130,11 @@ class AssignTagModelBloc
|
||||
|
||||
String? _getValidationError(List<TagModel> tags) {
|
||||
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
|
||||
if (hasEmptyTag) return 'Tags cannot be empty.';
|
||||
if (hasEmptyTag) {
|
||||
return 'Tags cannot be empty.';
|
||||
}
|
||||
|
||||
// Check for duplicate tags
|
||||
final duplicateTags = tags
|
||||
.map((tag) => tag.tag?.trim() ?? '')
|
||||
.fold<Map<String, int>>({}, (map, tag) {
|
||||
|
@ -5,7 +5,7 @@ abstract class AssignTagModelState extends Equatable {
|
||||
const AssignTagModelState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class AssignTagModelInitial extends AssignTagModelState {}
|
||||
@ -15,7 +15,7 @@ class AssignTagModelLoading extends AssignTagModelState {}
|
||||
class AssignTagModelLoaded extends AssignTagModelState {
|
||||
final List<TagModel> tags;
|
||||
final bool isSaveEnabled;
|
||||
final String? errorMessage;
|
||||
final String? errorMessage;
|
||||
|
||||
const AssignTagModelLoaded({
|
||||
required this.tags,
|
||||
@ -24,7 +24,7 @@ class AssignTagModelLoaded extends AssignTagModelState {
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [tags, isSaveEnabled];
|
||||
List<Object?> get props => [tags, isSaveEnabled, errorMessage];
|
||||
}
|
||||
|
||||
class AssignTagModelError extends AssignTagModelState {
|
||||
@ -33,5 +33,5 @@ class AssignTagModelError extends AssignTagModelState {
|
||||
const AssignTagModelError(this.errorMessage);
|
||||
|
||||
@override
|
||||
List<Object> get props => [errorMessage];
|
||||
List<Object?> get props => [errorMessage];
|
||||
}
|
||||
|
@ -9,22 +9,28 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_pr
|
||||
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
|
||||
|
||||
class AssignTagModelsDialog extends StatelessWidget {
|
||||
final List<ProductModel>? products;
|
||||
final List<SubspaceTemplateModel>? subspaces;
|
||||
final SpaceTemplateModel? spaceModel;
|
||||
|
||||
final List<TagModel> initialTags;
|
||||
final ValueChanged<List<TagModel>>? onTagsAssigned;
|
||||
final List<SelectedProduct> addedProducts;
|
||||
final List<String>? allTags;
|
||||
final String spaceName;
|
||||
final String title;
|
||||
final void Function(
|
||||
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces)? onUpdate;
|
||||
final BuildContext? pageContext;
|
||||
final List<String>? otherSpaceModels;
|
||||
final List<SpaceTemplateModel>? allSpaceModels;
|
||||
|
||||
const AssignTagModelsDialog(
|
||||
{Key? key,
|
||||
@ -36,7 +42,10 @@ class AssignTagModelsDialog extends StatelessWidget {
|
||||
this.allTags,
|
||||
required this.spaceName,
|
||||
required this.title,
|
||||
this.onUpdate})
|
||||
this.pageContext,
|
||||
this.otherSpaceModels,
|
||||
this.spaceModel,
|
||||
this.allSpaceModels})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
@ -47,296 +56,305 @@ class AssignTagModelsDialog extends StatelessWidget {
|
||||
..add('Main Space');
|
||||
|
||||
return BlocProvider(
|
||||
create: (_) => AssignTagModelBloc()
|
||||
..add(InitializeTagModels(
|
||||
initialTags: initialTags,
|
||||
addedProducts: addedProducts,
|
||||
)),
|
||||
child: BlocBuilder<AssignTagModelBloc, AssignTagModelState>(
|
||||
builder: (context, state) {
|
||||
if (state is AssignTagModelLoaded) {
|
||||
final controllers = List.generate(
|
||||
state.tags.length,
|
||||
(index) => TextEditingController(text: state.tags[index].tag),
|
||||
);
|
||||
create: (_) => AssignTagModelBloc()
|
||||
..add(InitializeTagModels(
|
||||
initialTags: initialTags,
|
||||
addedProducts: addedProducts,
|
||||
)),
|
||||
child: BlocListener<AssignTagModelBloc, AssignTagModelState>(
|
||||
listener: (context, state) {},
|
||||
child: BlocBuilder<AssignTagModelBloc, AssignTagModelState>(
|
||||
builder: (context, state) {
|
||||
if (state is AssignTagModelLoaded) {
|
||||
final controllers = List.generate(
|
||||
state.tags.length,
|
||||
(index) => TextEditingController(text: state.tags[index].tag),
|
||||
);
|
||||
|
||||
return AlertDialog(
|
||||
title: Text(title),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: DataTable(
|
||||
headingRowColor: WidgetStateProperty.all(
|
||||
ColorsManager.dataHeaderGrey),
|
||||
border: TableBorder.all(
|
||||
color: ColorsManager.dataHeaderGrey,
|
||||
width: 1,
|
||||
return AlertDialog(
|
||||
title: Text(title),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
ClipRRect(
|
||||
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,
|
||||
headingRowAlignment: MainAxisAlignment.start,
|
||||
label: Text('Tag',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium)),
|
||||
DataColumn(
|
||||
label: Text('Location',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium)),
|
||||
],
|
||||
rows: state.tags.isEmpty
|
||||
? [
|
||||
const DataRow(cells: [
|
||||
DataCell(
|
||||
Center(
|
||||
child: Text(
|
||||
'No Data Available',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: ColorsManager.lightGrayColor,
|
||||
child: DataTable(
|
||||
headingRowColor: WidgetStateProperty.all(
|
||||
ColorsManager.dataHeaderGrey),
|
||||
border: TableBorder.all(
|
||||
color: ColorsManager.dataHeaderGrey,
|
||||
width: 1,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
columns: [
|
||||
DataColumn(
|
||||
label: Text('#',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium)),
|
||||
DataColumn(
|
||||
label: Text('Device',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium)),
|
||||
DataColumn(
|
||||
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
|
||||
? [
|
||||
const DataRow(cells: [
|
||||
DataCell(
|
||||
Center(
|
||||
child: Text(
|
||||
'No Data Available',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color:
|
||||
ColorsManager.lightGrayColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell(SizedBox()),
|
||||
DataCell(SizedBox()),
|
||||
DataCell(SizedBox()),
|
||||
])
|
||||
]
|
||||
: List.generate(state.tags.length, (index) {
|
||||
final tag = state.tags[index];
|
||||
final controller = controllers[index];
|
||||
final availableTags = getAvailableTags(
|
||||
allTags ?? [], state.tags, tag);
|
||||
DataCell(SizedBox()),
|
||||
DataCell(SizedBox()),
|
||||
DataCell(SizedBox()),
|
||||
])
|
||||
]
|
||||
: List.generate(state.tags.length, (index) {
|
||||
final tag = state.tags[index];
|
||||
final controller = controllers[index];
|
||||
final availableTags = getAvailableTags(
|
||||
allTags ?? [], state.tags, tag);
|
||||
|
||||
return DataRow(
|
||||
cells: [
|
||||
DataCell(Text(index.toString())),
|
||||
DataCell(
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
tag.product?.name ?? 'Unknown',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)),
|
||||
const SizedBox(width: 10),
|
||||
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));
|
||||
},
|
||||
tooltip: 'Delete Tag',
|
||||
padding: EdgeInsets.zero,
|
||||
constraints:
|
||||
const BoxConstraints(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Container(
|
||||
width: 20.0,
|
||||
height: 20.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: ColorsManager
|
||||
.lightGrayColor,
|
||||
width: 1.0,
|
||||
alignment: Alignment
|
||||
.centerLeft, // Align cell content to the left
|
||||
child: SizedBox(
|
||||
width: double
|
||||
.infinity, // Ensure full width for dropdown
|
||||
child: DialogTextfieldDropdown(
|
||||
items: availableTags,
|
||||
initialValue: tag.tag,
|
||||
onSelected: (value) {
|
||||
controller.text = value;
|
||||
context
|
||||
.read<
|
||||
AssignTagModelBloc>()
|
||||
.add(UpdateTag(
|
||||
index: index,
|
||||
tag: value,
|
||||
));
|
||||
},
|
||||
),
|
||||
),
|
||||
child: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: ColorsManager
|
||||
.lightGreyColor,
|
||||
size: 16,
|
||||
),
|
||||
onPressed: () {
|
||||
context
|
||||
.read<AssignTagModelBloc>()
|
||||
.add(DeleteTagModel(
|
||||
tagToDelete: tag,
|
||||
tags: state.tags));
|
||||
},
|
||||
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, // Ensure full width for dropdown
|
||||
child: DialogTextfieldDropdown(
|
||||
items: availableTags,
|
||||
initialValue: tag.tag,
|
||||
onSelected: (value) {
|
||||
controller.text = value;
|
||||
context
|
||||
.read<AssignTagModelBloc>()
|
||||
.add(UpdateTag(
|
||||
index: index,
|
||||
tag: value,
|
||||
));
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: DialogDropdown(
|
||||
items: locations,
|
||||
selectedValue:
|
||||
tag.location ?? 'None',
|
||||
onSelected: (value) {
|
||||
context
|
||||
.read<AssignTagModelBloc>()
|
||||
.add(UpdateLocation(
|
||||
index: index,
|
||||
location: value,
|
||||
));
|
||||
},
|
||||
)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
if (state.errorMessage != null)
|
||||
Text(
|
||||
state.errorMessage!,
|
||||
style: const TextStyle(color: ColorsManager.warningRed),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Builder(
|
||||
builder: (buttonContext) => CancelButton(
|
||||
label: 'Add New Device',
|
||||
onPressed: () async {
|
||||
for (var tag in state.tags) {
|
||||
if (tag.location == null || subspaces == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final previousTagSubspace =
|
||||
checkTagExistInSubspace(tag, subspaces ?? []);
|
||||
|
||||
if (tag.location == 'Main Space') {
|
||||
removeTagFromSubspace(tag, previousTagSubspace);
|
||||
} else if (tag.location !=
|
||||
previousTagSubspace?.subspaceName) {
|
||||
removeTagFromSubspace(tag, previousTagSubspace);
|
||||
moveToNewSubspace(tag, subspaces ?? []);
|
||||
state.tags.removeWhere(
|
||||
(t) => t.internalId == tag.internalId);
|
||||
} else {
|
||||
updateTagInSubspace(tag, previousTagSubspace);
|
||||
state.tags.removeWhere(
|
||||
(t) => t.internalId == tag.internalId);
|
||||
}
|
||||
}
|
||||
if (context.mounted) {
|
||||
await showDialog<bool>(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (dialogContext) =>
|
||||
AddDeviceTypeModelWidget(
|
||||
products: products,
|
||||
subspaces: subspaces,
|
||||
isCreate: false,
|
||||
initialSelectedProducts: addedProducts,
|
||||
allTags: allTags,
|
||||
spaceName: spaceName,
|
||||
spaceTagModels: state.tags,
|
||||
onUpdate: (tags, subspaces) {
|
||||
onUpdate?.call(state.tags, subspaces);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
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)
|
||||
Text(
|
||||
state.errorMessage!,
|
||||
style: const TextStyle(
|
||||
color: ColorsManager.warningRed),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
borderRadius: 10,
|
||||
backgroundColor: state.isSaveEnabled
|
||||
? ColorsManager.secondaryColor
|
||||
: ColorsManager.grayColor,
|
||||
foregroundColor: ColorsManager.whiteColors,
|
||||
onPressed: state.isSaveEnabled
|
||||
? () async {
|
||||
Navigator.of(context).pop();
|
||||
),
|
||||
actions: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Builder(
|
||||
builder: (buttonContext) => CancelButton(
|
||||
label: 'Add New Device',
|
||||
onPressed: () async {
|
||||
final updatedTags =
|
||||
List<TagModel>.from(state.tags);
|
||||
final result =
|
||||
processTags(updatedTags, subspaces);
|
||||
|
||||
for (var tag in state.tags) {
|
||||
if (tag.location == null ||
|
||||
subspaces == null) {
|
||||
continue;
|
||||
}
|
||||
final processedTags =
|
||||
result['updatedTags'] as List<TagModel>;
|
||||
final processedSubspaces = result['subspaces'];
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
final previousTagSubspace =
|
||||
checkTagExistInSubspace(
|
||||
tag, subspaces ?? []);
|
||||
|
||||
if (tag.location == 'Main Space') {
|
||||
removeTagFromSubspace(
|
||||
tag, previousTagSubspace);
|
||||
} else if (tag.location !=
|
||||
previousTagSubspace?.subspaceName) {
|
||||
removeTagFromSubspace(
|
||||
tag, previousTagSubspace);
|
||||
moveToNewSubspace(tag, subspaces ?? []);
|
||||
state.tags.removeWhere(
|
||||
(t) => t.internalId == tag.internalId);
|
||||
} else {
|
||||
updateTagInSubspace(
|
||||
tag, previousTagSubspace);
|
||||
state.tags.removeWhere(
|
||||
(t) => t.internalId == tag.internalId);
|
||||
}
|
||||
await showDialog<bool>(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (dialogContext) =>
|
||||
AddDeviceTypeModelWidget(
|
||||
products: products,
|
||||
subspaces: subspaces,
|
||||
isCreate: false,
|
||||
initialSelectedProducts: TagHelper
|
||||
.createInitialSelectedProducts(
|
||||
processedTags,
|
||||
processedSubspaces),
|
||||
allTags: allTags,
|
||||
spaceName: spaceName,
|
||||
otherSpaceModels: otherSpaceModels,
|
||||
spaceTagModels: processedTags,
|
||||
pageContext: pageContext,
|
||||
spaceModel: SpaceTemplateModel(
|
||||
modelName: spaceName,
|
||||
tags: updatedTags,
|
||||
uuid: spaceModel?.uuid,
|
||||
internalId:
|
||||
spaceModel?.internalId,
|
||||
subspaceModels:
|
||||
processedSubspaces)),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
borderRadius: 10,
|
||||
backgroundColor: state.isSaveEnabled
|
||||
? ColorsManager.secondaryColor
|
||||
: ColorsManager.grayColor,
|
||||
foregroundColor: ColorsManager.whiteColors,
|
||||
onPressed: state.isSaveEnabled
|
||||
? () async {
|
||||
final updatedTags =
|
||||
List<TagModel>.from(state.tags);
|
||||
final result =
|
||||
processTags(updatedTags, subspaces);
|
||||
|
||||
onUpdate?.call(state.tags, subspaces);
|
||||
}
|
||||
: null,
|
||||
child: const Text('Save'),
|
||||
),
|
||||
final processedTags =
|
||||
result['updatedTags'] as List<TagModel>;
|
||||
final processedSubspaces =
|
||||
result['subspaces']
|
||||
as List<SubspaceTemplateModel>;
|
||||
|
||||
Navigator.of(context)
|
||||
.popUntil((route) => route.isFirst);
|
||||
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext dialogContext) {
|
||||
return CreateSpaceModelDialog(
|
||||
products: products,
|
||||
allSpaceModels: allSpaceModels,
|
||||
allTags: allTags,
|
||||
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),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
} else if (state is AssignTagModelLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else {
|
||||
return const Center(child: Text('Something went wrong.'));
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
);
|
||||
} else if (state is AssignTagModelLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else {
|
||||
return const Center(child: Text('Something went wrong.'));
|
||||
}
|
||||
},
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
List<String> getAvailableTags(
|
||||
@ -349,39 +367,109 @@ class AssignTagModelsDialog extends StatelessWidget {
|
||||
.toList();
|
||||
}
|
||||
|
||||
void removeTagFromSubspace(TagModel tag, SubspaceTemplateModel? subspace) {
|
||||
subspace?.tags?.removeWhere((t) => t.internalId == tag.internalId);
|
||||
}
|
||||
|
||||
SubspaceTemplateModel? checkTagExistInSubspace(
|
||||
int? checkTagExistInSubspace(
|
||||
TagModel tag, List<SubspaceTemplateModel>? subspaces) {
|
||||
if (subspaces == null) return null;
|
||||
for (var subspace in subspaces) {
|
||||
if (subspace.tags == null) return null;
|
||||
for (int i = 0; i < subspaces.length; i++) {
|
||||
final subspace = subspaces[i];
|
||||
if (subspace.tags == null) continue;
|
||||
for (var t in subspace.tags!) {
|
||||
if (tag.internalId == t.internalId) return subspace;
|
||||
if (tag.internalId == t.internalId) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void moveToNewSubspace(TagModel tag, List<SubspaceTemplateModel> subspaces) {
|
||||
final targetSubspace = subspaces
|
||||
.firstWhere((subspace) => subspace.subspaceName == tag.location);
|
||||
Map<String, dynamic> processTags(
|
||||
List<TagModel> updatedTags, List<SubspaceTemplateModel>? subspaces) {
|
||||
final modifiedTags = List<TagModel>.from(updatedTags);
|
||||
final modifiedSubspaces = List<SubspaceTemplateModel>.from(subspaces ?? []);
|
||||
|
||||
targetSubspace.tags ??= [];
|
||||
if (targetSubspace.tags?.any((t) => t.internalId == tag.internalId) !=
|
||||
true) {
|
||||
targetSubspace.tags?.add(tag);
|
||||
if (subspaces != null) {
|
||||
for (var subspace in subspaces) {
|
||||
subspace.tags?.removeWhere(
|
||||
(tag) => !modifiedTags
|
||||
.any((updatedTag) => updatedTag.internalId == tag.internalId),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateTagInSubspace(TagModel tag, SubspaceTemplateModel? subspace) {
|
||||
final currentTag = subspace?.tags?.firstWhere(
|
||||
(t) => t.internalId == tag.internalId,
|
||||
);
|
||||
if (currentTag != null) {
|
||||
currentTag.tag = tag.tag;
|
||||
for (var tag in modifiedTags.toList()) {
|
||||
if (modifiedSubspaces.isEmpty) continue;
|
||||
|
||||
final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces);
|
||||
|
||||
if ((tag.location == 'Main Space' || tag.location == null) &&
|
||||
(prevIndice == null ||
|
||||
modifiedSubspaces[prevIndice].subspaceName == 'Main Space')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((tag.location == 'Main Space' || tag.location == null) &&
|
||||
prevIndice != null) {
|
||||
modifiedSubspaces[prevIndice]
|
||||
.tags
|
||||
?.removeWhere((t) => t.internalId == tag.internalId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((tag.location != 'Main Space' && tag.location != null) &&
|
||||
prevIndice == null) {
|
||||
final newIndex = modifiedSubspaces
|
||||
.indexWhere((subspace) => subspace.subspaceName == tag.location);
|
||||
if (newIndex != -1) {
|
||||
if (modifiedSubspaces[newIndex]
|
||||
.tags
|
||||
?.any((t) => t.internalId == tag.internalId) !=
|
||||
true) {
|
||||
tag.location = modifiedSubspaces[newIndex].subspaceName;
|
||||
modifiedSubspaces[newIndex].tags?.add(tag);
|
||||
}
|
||||
}
|
||||
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((tag.location != 'Main Space' && tag.location != null) &&
|
||||
tag.location != modifiedSubspaces[prevIndice!].subspaceName) {
|
||||
modifiedSubspaces[prevIndice]
|
||||
.tags
|
||||
?.removeWhere((t) => t.internalId == tag.internalId);
|
||||
final newIndex = modifiedSubspaces
|
||||
.indexWhere((subspace) => subspace.subspaceName == tag.location);
|
||||
if (newIndex != -1) {
|
||||
if (modifiedSubspaces[newIndex]
|
||||
.tags
|
||||
?.any((t) => t.internalId == tag.internalId) !=
|
||||
true) {
|
||||
tag.location = modifiedSubspaces[newIndex].subspaceName;
|
||||
modifiedSubspaces[newIndex].tags?.add(tag);
|
||||
}
|
||||
}
|
||||
|
||||
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((tag.location != 'Main Space' && tag.location != null) &&
|
||||
tag.location == modifiedSubspaces[prevIndice!].subspaceName) {
|
||||
modifiedTags.removeWhere((t) => t.internalId == tag.internalId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((tag.location == 'Main Space' || tag.location == null) &&
|
||||
prevIndice != null) {
|
||||
modifiedSubspaces[prevIndice]
|
||||
.tags
|
||||
?.removeWhere((t) => t.internalId == tag.internalId);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
'updatedTags': modifiedTags,
|
||||
'subspaces': modifiedSubspaces,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -6,26 +6,42 @@ import 'subspace_event.dart';
|
||||
import 'subspace_state.dart';
|
||||
|
||||
class SubSpaceBloc extends Bloc<SubSpaceEvent, SubSpaceState> {
|
||||
SubSpaceBloc() : super(SubSpaceState([], [], '')) {
|
||||
SubSpaceBloc() : super(SubSpaceState([], [], '', {})) {
|
||||
on<AddSubSpace>((event, emit) {
|
||||
final existingNames =
|
||||
state.subSpaces.map((e) => e.subspaceName).toSet();
|
||||
final existingNames = state.subSpaces.map((e) => e.subspaceName).toSet();
|
||||
|
||||
if (existingNames.contains(event.subSpace.subspaceName.toLowerCase())) {
|
||||
emit(SubSpaceState(
|
||||
state.subSpaces,
|
||||
state.updatedSubSpaceModels,
|
||||
'Subspace name already exists.',
|
||||
));
|
||||
} else {
|
||||
final updatedDuplicates = Set<String>.from(state.duplicates)
|
||||
..add(event.subSpace.subspaceName.toLowerCase());
|
||||
final updatedSubSpaces = List<SubspaceModel>.from(state.subSpaces)
|
||||
..add(event.subSpace);
|
||||
|
||||
emit(SubSpaceState(
|
||||
updatedSubSpaces,
|
||||
state.updatedSubSpaceModels,
|
||||
'',
|
||||
'*Duplicated sub-space name',
|
||||
updatedDuplicates,
|
||||
));
|
||||
} else {
|
||||
// Add subspace if no duplicate exists
|
||||
final updatedSubSpaces = List<SubspaceModel>.from(state.subSpaces)
|
||||
..add(event.subSpace);
|
||||
|
||||
if (state.duplicates.isNotEmpty) {
|
||||
emit(SubSpaceState(
|
||||
updatedSubSpaces,
|
||||
state.updatedSubSpaceModels,
|
||||
'*Duplicated sub-space name',
|
||||
state.duplicates,
|
||||
));
|
||||
} else {
|
||||
emit(SubSpaceState(
|
||||
updatedSubSpaces,
|
||||
state.updatedSubSpaceModels,
|
||||
'',
|
||||
state.duplicates,
|
||||
// Clear error message
|
||||
));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -45,13 +61,51 @@ class SubSpaceBloc extends Bloc<SubSpaceEvent, SubSpaceState> {
|
||||
));
|
||||
}
|
||||
|
||||
final nameOccurrences = <String, int>{};
|
||||
for (final subSpace in updatedSubSpaces) {
|
||||
final lowerName = subSpace.subspaceName.toLowerCase();
|
||||
nameOccurrences[lowerName] = (nameOccurrences[lowerName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
final updatedDuplicates = nameOccurrences.entries
|
||||
.where((entry) => entry.value > 1)
|
||||
.map((entry) => entry.key)
|
||||
.toSet();
|
||||
final errorMessage =
|
||||
updatedDuplicates.isNotEmpty ? '*Duplicated sub-space name' : '';
|
||||
|
||||
emit(SubSpaceState(
|
||||
updatedSubSpaces,
|
||||
updatedSubspaceModels,
|
||||
'', // Clear error message
|
||||
errorMessage,
|
||||
updatedDuplicates,
|
||||
));
|
||||
});
|
||||
|
||||
// Handle UpdateSubSpace Event
|
||||
|
||||
on<UpdateSubSpace>((event, emit) {
|
||||
final updatedSubSpaces = state.subSpaces.map((subSpace) {
|
||||
if (subSpace.uuid == event.updatedSubSpace.uuid) {
|
||||
return event.updatedSubSpace;
|
||||
}
|
||||
return subSpace;
|
||||
}).toList();
|
||||
|
||||
final updatedSubspaceModels = List<UpdateSubspaceModel>.from(
|
||||
state.updatedSubSpaceModels,
|
||||
);
|
||||
|
||||
updatedSubspaceModels.add(UpdateSubspaceModel(
|
||||
action: Action.update,
|
||||
uuid: event.updatedSubSpace.uuid!,
|
||||
));
|
||||
|
||||
emit(SubSpaceState(
|
||||
updatedSubSpaces,
|
||||
updatedSubspaceModels,
|
||||
'',
|
||||
state.duplicates,
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -4,23 +4,26 @@ class SubSpaceState {
|
||||
final List<SubspaceModel> subSpaces;
|
||||
final List<UpdateSubspaceModel> updatedSubSpaceModels;
|
||||
final String errorMessage;
|
||||
final Set<String> duplicates;
|
||||
|
||||
SubSpaceState(
|
||||
this.subSpaces,
|
||||
this.updatedSubSpaceModels,
|
||||
this.errorMessage,
|
||||
this.duplicates,
|
||||
);
|
||||
|
||||
|
||||
SubSpaceState copyWith({
|
||||
List<SubspaceModel>? subSpaces,
|
||||
List<UpdateSubspaceModel>? updatedSubSpaceModels,
|
||||
String? errorMessage,
|
||||
Set<String>? duplicates,
|
||||
}) {
|
||||
return SubSpaceState(
|
||||
subSpaces ?? this.subSpaces,
|
||||
updatedSubSpaceModels ?? this.updatedSubSpaceModels,
|
||||
errorMessage ?? this.errorMessage,
|
||||
duplicates ?? this.duplicates,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -81,41 +81,64 @@ class CreateSubSpaceDialog extends StatelessWidget {
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
...state.subSpaces.map(
|
||||
(subSpace) => Chip(
|
||||
label: Text(
|
||||
subSpace.subspaceName,
|
||||
style: const TextStyle(
|
||||
color: ColorsManager.spaceColor),
|
||||
),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: const BorderSide(
|
||||
color: ColorsManager.transparentColor,
|
||||
width: 0,
|
||||
),
|
||||
),
|
||||
deleteIcon: Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
width: 1.5,
|
||||
...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 Chip(
|
||||
label: Text(
|
||||
subSpace.subspaceName,
|
||||
style: const TextStyle(
|
||||
color: ColorsManager.spaceColor,
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.close,
|
||||
size: 16,
|
||||
color: ColorsManager.lightGrayColor,
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
side: BorderSide(
|
||||
color: isDuplicate
|
||||
? ColorsManager.red
|
||||
: ColorsManager.transparentColor,
|
||||
width: 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
onDeleted: () => context
|
||||
.read<SubSpaceBloc>()
|
||||
.add(RemoveSubSpace(subSpace)),
|
||||
),
|
||||
deleteIcon: Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.close,
|
||||
size: 16,
|
||||
color: ColorsManager.lightGrayColor,
|
||||
),
|
||||
),
|
||||
onDeleted: () => context
|
||||
.read<SubSpaceBloc>()
|
||||
.add(RemoveSubSpace(subSpace)),
|
||||
);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
@ -142,27 +165,29 @@ class CreateSubSpaceDialog extends StatelessWidget {
|
||||
color: ColorsManager.blackColor),
|
||||
),
|
||||
),
|
||||
if (state.errorMessage.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
state.errorMessage,
|
||||
style: const TextStyle(
|
||||
color: ColorsManager.warningRed,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (state.errorMessage.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
state.errorMessage,
|
||||
style: const TextStyle(
|
||||
color: ColorsManager.warningRed,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CancelButton(
|
||||
label: 'Cancel',
|
||||
onPressed: () async {},
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
|
@ -25,22 +25,30 @@ class SubSpaceModelBloc extends Bloc<SubSpaceModelEvent, SubSpaceModelState> {
|
||||
updatedDuplicates,
|
||||
));
|
||||
} else {
|
||||
// Add subspace if no duplicate exists
|
||||
final updatedSubSpaces =
|
||||
List<SubspaceTemplateModel>.from(state.subSpaces)
|
||||
..add(event.subSpace);
|
||||
|
||||
emit(SubSpaceModelState(
|
||||
updatedSubSpaces,
|
||||
state.updatedSubSpaceModels,
|
||||
'',
|
||||
state.duplicates,
|
||||
// Clear error message
|
||||
));
|
||||
if (state.duplicates.isNotEmpty) {
|
||||
emit(SubSpaceModelState(
|
||||
updatedSubSpaces,
|
||||
state.updatedSubSpaceModels,
|
||||
'*Duplicated sub-space name',
|
||||
state.duplicates,
|
||||
));
|
||||
} else {
|
||||
emit(SubSpaceModelState(
|
||||
updatedSubSpaces,
|
||||
state.updatedSubSpaceModels,
|
||||
'',
|
||||
state.duplicates,
|
||||
));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle RemoveSubSpaceModel Event
|
||||
|
||||
on<RemoveSubSpaceModel>((event, emit) {
|
||||
final updatedSubSpaces = List<SubspaceTemplateModel>.from(state.subSpaces)
|
||||
..remove(event.subSpace);
|
||||
@ -48,16 +56,6 @@ class SubSpaceModelBloc extends Bloc<SubSpaceModelEvent, SubSpaceModelState> {
|
||||
final updatedSubspaceModels = List<UpdateSubspaceTemplateModel>.from(
|
||||
state.updatedSubSpaceModels,
|
||||
);
|
||||
final nameOccurrences = <String, int>{};
|
||||
for (final subSpace in updatedSubSpaces) {
|
||||
final lowerName = subSpace.subspaceName.toLowerCase();
|
||||
nameOccurrences[lowerName] = (nameOccurrences[lowerName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
final updatedDuplicates = nameOccurrences.entries
|
||||
.where((entry) => entry.value > 1)
|
||||
.map((entry) => entry.key)
|
||||
.toSet();
|
||||
|
||||
if (event.subSpace.uuid?.isNotEmpty ?? false) {
|
||||
updatedSubspaceModels.add(UpdateSubspaceTemplateModel(
|
||||
@ -66,12 +64,28 @@ class SubSpaceModelBloc extends Bloc<SubSpaceModelEvent, SubSpaceModelState> {
|
||||
));
|
||||
}
|
||||
|
||||
// Count occurrences of sub-space names to identify duplicates
|
||||
final nameOccurrences = <String, int>{};
|
||||
for (final subSpace in updatedSubSpaces) {
|
||||
final lowerName = subSpace.subspaceName.toLowerCase();
|
||||
nameOccurrences[lowerName] = (nameOccurrences[lowerName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
// Identify duplicate names
|
||||
final updatedDuplicates = nameOccurrences.entries
|
||||
.where((entry) => entry.value > 1)
|
||||
.map((entry) => entry.key)
|
||||
.toSet();
|
||||
|
||||
// Determine the error message
|
||||
final errorMessage =
|
||||
updatedDuplicates.isNotEmpty ? '*Duplicated sub-space name' : '';
|
||||
|
||||
emit(SubSpaceModelState(
|
||||
updatedSubSpaces,
|
||||
updatedSubspaceModels,
|
||||
'',
|
||||
errorMessage,
|
||||
updatedDuplicates,
|
||||
// Clear error message
|
||||
));
|
||||
});
|
||||
|
||||
|
@ -186,8 +186,7 @@ class CreateSubSpaceModelDialog extends StatelessWidget {
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
onPressed: (state.subSpaces.isEmpty ||
|
||||
state.errorMessage.isNotEmpty)
|
||||
onPressed: (state.errorMessage.isNotEmpty)
|
||||
? null
|
||||
: () async {
|
||||
final subSpaces = context
|
||||
@ -201,8 +200,7 @@ class CreateSubSpaceModelDialog extends StatelessWidget {
|
||||
},
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
borderRadius: 10,
|
||||
foregroundColor: state.subSpaces.isEmpty ||
|
||||
state.errorMessage.isNotEmpty
|
||||
foregroundColor: state.errorMessage.isNotEmpty
|
||||
? ColorsManager.whiteColorsWithOpacity
|
||||
: ColorsManager.whiteColors,
|
||||
child: const Text('OK'),
|
||||
|
@ -1,5 +1,8 @@
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.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/subspace_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
||||
|
||||
@ -17,27 +20,57 @@ class TagHelper {
|
||||
if (subspaces != null) {
|
||||
for (var subspace in subspaces) {
|
||||
if (subspace.tags != null) {
|
||||
for (var existingTag in subspace.tags!) {
|
||||
initialTags.addAll(
|
||||
subspace.tags!.map(
|
||||
(tag) => tag.copyWith(
|
||||
location: subspace.subspaceName,
|
||||
internalId: existingTag.internalId,
|
||||
tag: existingTag.tag),
|
||||
initialTags.addAll(
|
||||
subspace.tags!.map(
|
||||
(tag) => tag.copyWith(
|
||||
location: subspace.subspaceName,
|
||||
internalId: tag.internalId,
|
||||
tag: tag.tag,
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return initialTags;
|
||||
}
|
||||
|
||||
static Map<ProductModel, int> groupTags(List<TagModel> tags) {
|
||||
static List<Tag> generateInitialForTags({
|
||||
List<Tag>? spaceTags,
|
||||
List<SubspaceModel>? subspaces,
|
||||
}) {
|
||||
final List<Tag> initialTags = [];
|
||||
|
||||
if (spaceTags != null) {
|
||||
initialTags.addAll(spaceTags);
|
||||
}
|
||||
|
||||
if (subspaces != null) {
|
||||
for (var subspace in subspaces) {
|
||||
if (subspace.tags != null) {
|
||||
initialTags.addAll(
|
||||
subspace.tags!.map(
|
||||
(tag) => tag.copyWith(
|
||||
location: subspace.subspaceName,
|
||||
internalId: tag.internalId,
|
||||
tag: tag.tag,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return initialTags;
|
||||
}
|
||||
|
||||
static Map<ProductModel, int> groupTags(List<BaseTag> tags) {
|
||||
final Map<ProductModel, int> groupedTags = {};
|
||||
for (var tag in tags) {
|
||||
if (tag.product != null) {
|
||||
groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1;
|
||||
final product = tag.product!;
|
||||
groupedTags[product] = (groupedTags[product] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
return groupedTags;
|
||||
@ -77,4 +110,39 @@ class TagHelper {
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
|
||||
static List<SelectedProduct> createInitialSelectedProductsForTags(
|
||||
List<Tag>? tags, List<SubspaceModel>? subspaces) {
|
||||
final Map<ProductModel, int> productCounts = {};
|
||||
|
||||
if (tags != null) {
|
||||
for (var tag in tags) {
|
||||
if (tag.product != null) {
|
||||
productCounts[tag.product!] = (productCounts[tag.product!] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (subspaces != null) {
|
||||
for (var subspace in subspaces) {
|
||||
if (subspace.tags != null) {
|
||||
for (var tag in subspace.tags!) {
|
||||
if (tag.product != null) {
|
||||
productCounts[tag.product!] =
|
||||
(productCounts[tag.product!] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return productCounts.entries
|
||||
.map((entry) => SelectedProduct(
|
||||
productId: entry.key.uuid,
|
||||
count: entry.value,
|
||||
productName: entry.key.name ?? 'Unnamed',
|
||||
product: entry.key,
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,9 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/create_sp
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';
|
||||
import 'package:syncrow_web/services/space_model_mang_api.dart';
|
||||
import 'package:syncrow_web/utils/constants/action_enum.dart';
|
||||
|
||||
class CreateSpaceModelBloc
|
||||
extends Bloc<CreateSpaceModelEvent, CreateSpaceModelState> {
|
||||
@ -66,8 +68,11 @@ class CreateSpaceModelBloc
|
||||
|
||||
on<UpdateSpaceTemplate>((event, emit) {
|
||||
_space = event.spaceTemplate;
|
||||
emit(CreateSpaceModelLoaded(_space!));
|
||||
final String? errorMessage = _checkDuplicateModelName(
|
||||
event.allModels ?? [], event.spaceTemplate.modelName);
|
||||
emit(CreateSpaceModelLoaded(_space!, errorMessage: errorMessage));
|
||||
});
|
||||
|
||||
on<AddSubspacesToSpaceTemplate>((event, emit) {
|
||||
final currentState = state;
|
||||
|
||||
@ -130,7 +135,8 @@ class CreateSpaceModelBloc
|
||||
final updatedSpace =
|
||||
currentState.space.copyWith(subspaceModels: updatedSubspaces);
|
||||
|
||||
emit(CreateSpaceModelLoaded(updatedSpace));
|
||||
emit(CreateSpaceModelLoaded(updatedSpace,
|
||||
errorMessage: currentState.errorMessage));
|
||||
} else {
|
||||
emit(CreateSpaceModelError("Space template not initialized"));
|
||||
}
|
||||
@ -172,7 +178,12 @@ class CreateSpaceModelBloc
|
||||
on<UpdateSpaceTemplateName>((event, emit) {
|
||||
final currentState = state;
|
||||
if (currentState is CreateSpaceModelLoaded) {
|
||||
if (event.name.trim().isEmpty) {
|
||||
if (event.allModels.contains(event.name) == true) {
|
||||
emit(CreateSpaceModelLoaded(
|
||||
currentState.space,
|
||||
errorMessage: "Duplicate Model name",
|
||||
));
|
||||
} else if (event.name.trim().isEmpty) {
|
||||
emit(CreateSpaceModelLoaded(
|
||||
currentState.space,
|
||||
errorMessage: "Model name cannot be empty",
|
||||
@ -187,5 +198,189 @@ class CreateSpaceModelBloc
|
||||
emit(CreateSpaceModelError("Space template not initialized"));
|
||||
}
|
||||
});
|
||||
|
||||
on<ModifySpaceTemplate>((event, emit) async {
|
||||
try {
|
||||
if (event.spaceTemplate.uuid != null) {
|
||||
final prevSpaceModel =
|
||||
await _api.getSpaceModel(event.spaceTemplate.uuid ?? '');
|
||||
|
||||
final newSpaceModel = event.updatedSpaceTemplate;
|
||||
String? spaceModelName;
|
||||
if (prevSpaceModel?.modelName != newSpaceModel.modelName) {
|
||||
spaceModelName = newSpaceModel.modelName;
|
||||
}
|
||||
List<TagModelUpdate> tagUpdates = [];
|
||||
final List<UpdateSubspaceTemplateModel> subspaceUpdates = [];
|
||||
final List<SubspaceTemplateModel>? prevSubspaces =
|
||||
prevSpaceModel?.subspaceModels;
|
||||
final List<SubspaceTemplateModel>? newSubspaces =
|
||||
newSpaceModel.subspaceModels;
|
||||
|
||||
tagUpdates =
|
||||
processTagUpdates(prevSpaceModel?.tags, newSpaceModel.tags);
|
||||
|
||||
if (prevSubspaces != null || newSubspaces != null) {
|
||||
if (prevSubspaces != null && newSubspaces != null) {
|
||||
for (var prevSubspace in prevSubspaces) {
|
||||
final existsInNew = newSubspaces
|
||||
.any((subspace) => subspace.uuid == prevSubspace.uuid);
|
||||
if (!existsInNew) {
|
||||
subspaceUpdates.add(UpdateSubspaceTemplateModel(
|
||||
action: Action.delete, uuid: prevSubspace.uuid));
|
||||
}
|
||||
}
|
||||
} else if (prevSubspaces != null && newSubspaces == null) {
|
||||
for (var prevSubspace in prevSubspaces) {
|
||||
subspaceUpdates.add(UpdateSubspaceTemplateModel(
|
||||
action: Action.delete, uuid: prevSubspace.uuid));
|
||||
}
|
||||
}
|
||||
|
||||
if (newSubspaces != null) {
|
||||
for (var newSubspace in newSubspaces!) {
|
||||
// Tag without UUID
|
||||
if ((newSubspace.uuid == null || newSubspace.uuid!.isEmpty)) {
|
||||
final List<TagModelUpdate> tagUpdates = [];
|
||||
|
||||
if (newSubspace.tags != null) {
|
||||
for (var tag in newSubspace.tags!) {
|
||||
tagUpdates.add(TagModelUpdate(
|
||||
action: Action.add,
|
||||
uuid: tag.uuid == '' ? null : tag.uuid,
|
||||
tag: tag.tag,
|
||||
productUuid: tag.product?.uuid));
|
||||
}
|
||||
}
|
||||
subspaceUpdates.add(UpdateSubspaceTemplateModel(
|
||||
action: Action.add,
|
||||
subspaceName: newSubspace.subspaceName,
|
||||
tags: tagUpdates));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (prevSubspaces != null && newSubspaces != null) {
|
||||
final newSubspaceMap = {
|
||||
for (var subspace in newSubspaces!) subspace.uuid: subspace
|
||||
};
|
||||
|
||||
for (var prevSubspace in prevSubspaces) {
|
||||
final newSubspace = newSubspaceMap[prevSubspace.uuid];
|
||||
|
||||
if (newSubspace != null) {
|
||||
final List<TagModelUpdate> tagSubspaceUpdates =
|
||||
processTagUpdates(prevSubspace.tags, newSubspace.tags);
|
||||
subspaceUpdates.add(UpdateSubspaceTemplateModel(
|
||||
action: Action.update,
|
||||
uuid: newSubspace.uuid,
|
||||
subspaceName: newSubspace.subspaceName,
|
||||
tags: tagSubspaceUpdates));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final spaceModelBody = CreateSpaceTemplateBodyModel(
|
||||
modelName: spaceModelName,
|
||||
tags: tagUpdates,
|
||||
subspaceModels: subspaceUpdates);
|
||||
|
||||
final res = await _api.updateSpaceModel(
|
||||
spaceModelBody, prevSpaceModel?.uuid ?? '');
|
||||
|
||||
if (res != null) {
|
||||
emit(CreateSpaceModelLoaded(newSpaceModel));
|
||||
if (event.onUpdate != null) {
|
||||
event.onUpdate!(event.updatedSpaceTemplate);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
emit(CreateSpaceModelError('Error creating space model'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
List<TagModelUpdate> processTagUpdates(
|
||||
List<TagModel>? prevTags,
|
||||
List<TagModel>? newTags,
|
||||
) {
|
||||
final List<TagModelUpdate> tagUpdates = [];
|
||||
final processedTags = <String?>{};
|
||||
|
||||
if (prevTags == null && newTags != null) {
|
||||
for (var newTag in newTags) {
|
||||
tagUpdates.add(TagModelUpdate(
|
||||
action: Action.add,
|
||||
tag: newTag.tag,
|
||||
uuid: newTag.uuid,
|
||||
productUuid: newTag.product?.uuid,
|
||||
));
|
||||
}
|
||||
return tagUpdates;
|
||||
}
|
||||
|
||||
if (newTags != null || prevTags != null) {
|
||||
// Case 1: Tags deleted
|
||||
if (prevTags != null && newTags != null) {
|
||||
for (var prevTag in prevTags) {
|
||||
final existsInNew =
|
||||
newTags!.any((newTag) => newTag.uuid == prevTag.uuid);
|
||||
if (!existsInNew) {
|
||||
tagUpdates
|
||||
.add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid));
|
||||
}
|
||||
}
|
||||
} else if (prevTags != null && newTags == null) {
|
||||
for (var prevTag in prevTags) {
|
||||
tagUpdates
|
||||
.add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid));
|
||||
}
|
||||
}
|
||||
|
||||
// Case 2: Tags added
|
||||
if (newTags != null) {
|
||||
final prevTagUuids = prevTags?.map((t) => t.uuid).toSet() ?? {};
|
||||
|
||||
for (var newTag in newTags!) {
|
||||
// Tag without UUID
|
||||
if ((newTag.uuid == null || !prevTagUuids.contains(newTag.uuid)) &&
|
||||
!processedTags.contains(newTag.tag)) {
|
||||
tagUpdates.add(TagModelUpdate(
|
||||
action: Action.add,
|
||||
tag: newTag.tag,
|
||||
uuid: newTag.uuid == '' ? null : newTag.uuid,
|
||||
productUuid: newTag.product?.uuid));
|
||||
processedTags.add(newTag.tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Case 3: Tags updated
|
||||
if (prevTags != null && newTags != null) {
|
||||
final newTagMap = {for (var tag in newTags!) tag.uuid: tag};
|
||||
|
||||
for (var prevTag in prevTags!) {
|
||||
final newTag = newTagMap[prevTag.uuid];
|
||||
if (newTag != null) {
|
||||
tagUpdates.add(TagModelUpdate(
|
||||
action: Action.update,
|
||||
uuid: newTag.uuid,
|
||||
tag: newTag.tag,
|
||||
));
|
||||
} else {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tagUpdates;
|
||||
}
|
||||
|
||||
String? _checkDuplicateModelName(List<String> allModels, String name) {
|
||||
if (allModels.contains(name)) {
|
||||
return "Duplicate Model name";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -14,15 +14,15 @@ class LoadSpaceTemplate extends CreateSpaceModelEvent {}
|
||||
|
||||
class UpdateSpaceTemplate extends CreateSpaceModelEvent {
|
||||
final SpaceTemplateModel spaceTemplate;
|
||||
List<String>? allModels;
|
||||
|
||||
UpdateSpaceTemplate(this.spaceTemplate);
|
||||
UpdateSpaceTemplate(this.spaceTemplate,this.allModels);
|
||||
}
|
||||
|
||||
class CreateSpaceTemplate extends CreateSpaceModelEvent {
|
||||
final SpaceTemplateModel spaceTemplate;
|
||||
final Function(SpaceTemplateModel)? onCreate;
|
||||
|
||||
|
||||
const CreateSpaceTemplate({
|
||||
required this.spaceTemplate,
|
||||
this.onCreate,
|
||||
@ -34,11 +34,12 @@ class CreateSpaceTemplate extends CreateSpaceModelEvent {
|
||||
|
||||
class UpdateSpaceTemplateName extends CreateSpaceModelEvent {
|
||||
final String name;
|
||||
final List<String> allModels;
|
||||
|
||||
UpdateSpaceTemplateName({required this.name});
|
||||
UpdateSpaceTemplateName({required this.name, required this.allModels});
|
||||
|
||||
@override
|
||||
List<Object> get props => [name];
|
||||
List<Object> get props => [name, allModels];
|
||||
}
|
||||
|
||||
class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent {
|
||||
@ -53,9 +54,19 @@ class AddTagsToSpaceTemplate extends CreateSpaceModelEvent {
|
||||
AddTagsToSpaceTemplate(this.tags);
|
||||
}
|
||||
|
||||
|
||||
class ValidateSpaceTemplateName extends CreateSpaceModelEvent {
|
||||
final String name;
|
||||
|
||||
ValidateSpaceTemplateName({required this.name});
|
||||
}
|
||||
|
||||
class ModifySpaceTemplate extends CreateSpaceModelEvent {
|
||||
final SpaceTemplateModel spaceTemplate;
|
||||
final SpaceTemplateModel updatedSpaceTemplate;
|
||||
final Function(SpaceTemplateModel)? onUpdate;
|
||||
|
||||
ModifySpaceTemplate(
|
||||
{required this.spaceTemplate,
|
||||
required this.updatedSpaceTemplate,
|
||||
this.onUpdate});
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> {
|
||||
required List<SpaceTemplateModel> initialSpaceModels,
|
||||
}) : super(SpaceModelLoaded(spaceModels: initialSpaceModels)) {
|
||||
on<CreateSpaceModel>(_onCreateSpaceModel);
|
||||
on<UpdateSpaceModel>(_onUpdateSpaceModel);
|
||||
}
|
||||
|
||||
Future<void> _onCreateSpaceModel(
|
||||
@ -33,4 +34,23 @@ class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onUpdateSpaceModel(
|
||||
UpdateSpaceModel event, Emitter<SpaceModelState> emit) async {
|
||||
final currentState = state;
|
||||
if (currentState is SpaceModelLoaded) {
|
||||
try {
|
||||
final newSpaceModel =
|
||||
await api.getSpaceModel(event.spaceModelUuid ?? '');
|
||||
if (newSpaceModel != null) {
|
||||
final updatedSpaceModels = currentState.spaceModels.map((model) {
|
||||
return model.uuid == event.spaceModelUuid ? newSpaceModel : model;
|
||||
}).toList();
|
||||
emit(SpaceModelLoaded(spaceModels: updatedSpaceModels));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(SpaceModelError(message: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,3 +16,21 @@ class CreateSpaceModel extends SpaceModelEvent {
|
||||
@override
|
||||
List<Object?> get props => [newSpaceModel];
|
||||
}
|
||||
|
||||
class GetSpaceModel extends SpaceModelEvent {
|
||||
final String spaceModelUuid;
|
||||
|
||||
GetSpaceModel({required this.spaceModelUuid});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [spaceModelUuid];
|
||||
}
|
||||
|
||||
class UpdateSpaceModel extends SpaceModelEvent {
|
||||
final String spaceModelUuid;
|
||||
|
||||
UpdateSpaceModel({required this.spaceModelUuid});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [spaceModelUuid];
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
class TagBodyModel {
|
||||
late String uuid;
|
||||
late String? uuid;
|
||||
late String tag;
|
||||
late final String? productUuid;
|
||||
|
||||
@ -30,12 +30,12 @@ class CreateSubspaceTemplateModel {
|
||||
}
|
||||
|
||||
class CreateSpaceTemplateBodyModel {
|
||||
final String modelName;
|
||||
final String? modelName;
|
||||
final List<dynamic>? tags;
|
||||
final List<dynamic>? subspaceModels;
|
||||
|
||||
CreateSpaceTemplateBodyModel({
|
||||
required this.modelName,
|
||||
this.modelName,
|
||||
this.tags,
|
||||
this.subspaceModels,
|
||||
});
|
||||
@ -47,4 +47,9 @@ class CreateSpaceTemplateBodyModel {
|
||||
'subspaceModels': subspaceModels,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return toJson().toString();
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart';
|
||||
import 'package:syncrow_web/utils/constants/action_enum.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
@ -70,14 +71,14 @@ class SpaceTemplateModel extends Equatable {
|
||||
}
|
||||
|
||||
class UpdateSubspaceTemplateModel {
|
||||
final String uuid;
|
||||
final String? uuid;
|
||||
final Action action;
|
||||
final String? subspaceName;
|
||||
final List<UpdateTagModel>? tags;
|
||||
final List<TagModelUpdate>? tags;
|
||||
|
||||
UpdateSubspaceTemplateModel({
|
||||
required this.action,
|
||||
required this.uuid,
|
||||
this.uuid,
|
||||
this.subspaceName,
|
||||
this.tags,
|
||||
});
|
||||
@ -88,7 +89,7 @@ class UpdateSubspaceTemplateModel {
|
||||
uuid: json['uuid'] ?? '',
|
||||
subspaceName: json['subspaceName'] ?? '',
|
||||
tags: (json['tags'] as List)
|
||||
.map((item) => UpdateTagModel.fromJson(item))
|
||||
.map((item) => TagModelUpdate.fromJson(item))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
@ -103,44 +104,6 @@ class UpdateSubspaceTemplateModel {
|
||||
}
|
||||
}
|
||||
|
||||
class UpdateTagModel {
|
||||
final Action action;
|
||||
final String? uuid;
|
||||
final String tag;
|
||||
final bool disabled;
|
||||
final ProductModel? product;
|
||||
|
||||
UpdateTagModel({
|
||||
required this.action,
|
||||
this.uuid,
|
||||
required this.tag,
|
||||
required this.disabled,
|
||||
this.product,
|
||||
});
|
||||
|
||||
factory UpdateTagModel.fromJson(Map<String, dynamic> json) {
|
||||
return UpdateTagModel(
|
||||
action: ActionExtension.fromValue(json['action']),
|
||||
uuid: json['uuid'] ?? '',
|
||||
tag: json['tag'] ?? '',
|
||||
disabled: json['disabled'] ?? false,
|
||||
product: json['product'] != null
|
||||
? ProductModel.fromMap(json['product'])
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'action': action.value,
|
||||
'uuid': uuid,
|
||||
'tag': tag,
|
||||
'disabled': disabled,
|
||||
'product': product?.toMap(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
extension SpaceTemplateExtensions on SpaceTemplateModel {
|
||||
List<String> listAllTagValues() {
|
||||
final List<String> tagValues = [];
|
||||
|
@ -20,7 +20,7 @@ class SubspaceTemplateModel {
|
||||
final String internalId = json['internalId'] ?? const Uuid().v4();
|
||||
|
||||
return SubspaceTemplateModel(
|
||||
uuid: json['uuid'] ?? '',
|
||||
uuid: json['uuid'],
|
||||
subspaceName: json['subspaceName'] ?? '',
|
||||
internalId: internalId,
|
||||
disabled: json['disabled'] ?? false,
|
||||
|
@ -1,27 +1,27 @@
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class TagModel {
|
||||
String? uuid;
|
||||
String? tag;
|
||||
final ProductModel? product;
|
||||
String internalId;
|
||||
String? location;
|
||||
|
||||
TagModel(
|
||||
{this.uuid,
|
||||
required this.tag,
|
||||
this.product,
|
||||
String? internalId,
|
||||
this.location})
|
||||
: internalId = internalId ?? const Uuid().v4();
|
||||
|
||||
class TagModel extends BaseTag {
|
||||
TagModel({
|
||||
String? uuid,
|
||||
required String? tag,
|
||||
ProductModel? product,
|
||||
String? internalId,
|
||||
String? location,
|
||||
}) : super(
|
||||
uuid: uuid,
|
||||
tag: tag,
|
||||
product: product,
|
||||
internalId: internalId,
|
||||
location: location,
|
||||
);
|
||||
factory TagModel.fromJson(Map<String, dynamic> json) {
|
||||
final String internalId = json['internalId'] ?? const Uuid().v4();
|
||||
|
||||
return TagModel(
|
||||
uuid: json['uuid'] ?? '',
|
||||
uuid: json['uuid'] ,
|
||||
internalId: internalId,
|
||||
tag: json['tag'] ?? '',
|
||||
product: json['product'] != null
|
||||
@ -30,16 +30,19 @@ class TagModel {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TagModel copyWith(
|
||||
{String? tag,
|
||||
ProductModel? product,
|
||||
String? uuid,
|
||||
String? location,
|
||||
String? internalId}) {
|
||||
String? internalId}) {
|
||||
return TagModel(
|
||||
tag: tag ?? this.tag,
|
||||
product: product ?? this.product,
|
||||
location: location ?? this.location,
|
||||
internalId: internalId ?? this.internalId,
|
||||
uuid:uuid?? this.uuid
|
||||
);
|
||||
}
|
||||
|
||||
@ -55,7 +58,7 @@ class TagModel {
|
||||
extension TagModelExtensions on TagModel {
|
||||
TagBodyModel toTagBodyModel() {
|
||||
return TagBodyModel()
|
||||
..uuid = uuid ?? ''
|
||||
..uuid = uuid
|
||||
..tag = tag ?? ''
|
||||
..productUuid = product?.uuid;
|
||||
}
|
||||
|
@ -0,0 +1,34 @@
|
||||
import 'package:syncrow_web/utils/constants/action_enum.dart';
|
||||
|
||||
class TagModelUpdate {
|
||||
final Action action;
|
||||
final String? uuid;
|
||||
final String? tag;
|
||||
final String? productUuid;
|
||||
|
||||
TagModelUpdate({
|
||||
required this.action,
|
||||
this.uuid,
|
||||
this.tag,
|
||||
this.productUuid,
|
||||
});
|
||||
|
||||
factory TagModelUpdate.fromJson(Map<String, dynamic> json) {
|
||||
return TagModelUpdate(
|
||||
action: json['action'],
|
||||
uuid: json['uuid'],
|
||||
tag: json['tag'],
|
||||
productUuid: json['productUuid'],
|
||||
);
|
||||
}
|
||||
|
||||
// Method to convert an instance to JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'action': action.value,
|
||||
'uuid': uuid, // Nullable field
|
||||
'tag': tag,
|
||||
'productUuid': productUuid,
|
||||
};
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_state.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/add_space_model_widget.dart';
|
||||
@ -24,6 +23,7 @@ class SpaceModelPage extends StatelessWidget {
|
||||
} else if (state is SpaceModelLoaded) {
|
||||
final spaceModels = state.spaceModels;
|
||||
final allTagValues = _getAllTagValues(spaceModels);
|
||||
final allSpaceModelNames = _getAllSpaceModelName(spaceModels);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
@ -52,12 +52,8 @@ class SpaceModelPage extends StatelessWidget {
|
||||
return CreateSpaceModelDialog(
|
||||
products: products,
|
||||
allTags: allTagValues,
|
||||
onLoad: (newModel) {
|
||||
context.read<SpaceModelBloc>().add(
|
||||
CreateSpaceModel(
|
||||
newSpaceModel: newModel),
|
||||
);
|
||||
},
|
||||
pageContext: context,
|
||||
otherSpaceModels: allSpaceModelNames,
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -67,6 +63,9 @@ class SpaceModelPage extends StatelessWidget {
|
||||
}
|
||||
// Render existing space model
|
||||
final model = spaceModels[index];
|
||||
final otherModel =
|
||||
List<String>.from(allSpaceModelNames);
|
||||
otherModel.remove(model.modelName);
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
@ -76,7 +75,9 @@ class SpaceModelPage extends StatelessWidget {
|
||||
products: products,
|
||||
allTags: allTagValues,
|
||||
spaceModel: model,
|
||||
onLoad: (newModel) {},
|
||||
otherSpaceModels: otherModel,
|
||||
pageContext: context,
|
||||
allSpaceModels: spaceModels,
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -128,4 +129,12 @@ class SpaceModelPage extends StatelessWidget {
|
||||
}
|
||||
return allTags;
|
||||
}
|
||||
|
||||
List<String> _getAllSpaceModelName(List<SpaceTemplateModel> spaceModels) {
|
||||
final List<String> names = [];
|
||||
for (final spaceModel in spaceModels) {
|
||||
names.add(spaceModel.modelName);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class ButtonContentWidget extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final IconData? icon;
|
||||
final String label;
|
||||
final String? svgAssets;
|
||||
|
||||
const ButtonContentWidget({
|
||||
Key? key,
|
||||
required this.icon,
|
||||
required this.label,
|
||||
}) : super(key: key);
|
||||
const ButtonContentWidget(
|
||||
{Key? key, this.icon, required this.label, this.svgAssets})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -30,10 +30,20 @@ class ButtonContentWidget extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
color: ColorsManager.spaceColor,
|
||||
),
|
||||
if (icon != null)
|
||||
Icon(
|
||||
icon,
|
||||
color: ColorsManager.spaceColor,
|
||||
),
|
||||
if (svgAssets != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 6.0),
|
||||
child: SvgPicture.asset(
|
||||
svgAssets!,
|
||||
width: screenWidth * 0.015, // Adjust icon size
|
||||
height: screenWidth * 0.015,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
|
@ -6,8 +6,11 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_mod
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_state.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/bloc/space_model_event.dart';
|
||||
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart';
|
||||
import 'package:syncrow_web/services/space_model_mang_api.dart';
|
||||
@ -17,15 +20,19 @@ class CreateSpaceModelDialog extends StatelessWidget {
|
||||
final List<ProductModel>? products;
|
||||
final List<String>? allTags;
|
||||
final SpaceTemplateModel? spaceModel;
|
||||
final void Function(SpaceTemplateModel newModel)? onLoad;
|
||||
final BuildContext? pageContext;
|
||||
final List<String>? otherSpaceModels;
|
||||
final List<SpaceTemplateModel>? allSpaceModels;
|
||||
|
||||
const CreateSpaceModelDialog({
|
||||
Key? key,
|
||||
this.products,
|
||||
this.allTags,
|
||||
this.spaceModel,
|
||||
this.onLoad,
|
||||
}) : super(key: key);
|
||||
const CreateSpaceModelDialog(
|
||||
{Key? key,
|
||||
this.products,
|
||||
this.allTags,
|
||||
this.spaceModel,
|
||||
this.pageContext,
|
||||
this.otherSpaceModels,
|
||||
this.allSpaceModels})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -44,17 +51,21 @@ class CreateSpaceModelDialog extends StatelessWidget {
|
||||
child: BlocProvider(
|
||||
create: (_) {
|
||||
final bloc = CreateSpaceModelBloc(_spaceModelApi);
|
||||
if (spaceModel != null) {
|
||||
bloc.add(UpdateSpaceTemplate(spaceModel!));
|
||||
} else {
|
||||
bloc.add(UpdateSpaceTemplate(SpaceTemplateModel(
|
||||
modelName: '',
|
||||
subspaceModels: const [],
|
||||
)));
|
||||
}
|
||||
|
||||
if (spaceModel != null) {
|
||||
bloc.add(UpdateSpaceTemplate(spaceModel!, otherSpaceModels));
|
||||
} else {
|
||||
bloc.add(UpdateSpaceTemplate(
|
||||
SpaceTemplateModel(
|
||||
modelName: '',
|
||||
subspaceModels: const [],
|
||||
),
|
||||
otherSpaceModels));
|
||||
}
|
||||
|
||||
spaceNameController.addListener(() {
|
||||
bloc.add(UpdateSpaceTemplateName(name: spaceNameController.text));
|
||||
bloc.add(UpdateSpaceTemplateName(
|
||||
name: spaceNameController.text,
|
||||
allModels: otherSpaceModels ?? []));
|
||||
});
|
||||
|
||||
return bloc;
|
||||
@ -86,18 +97,24 @@ class CreateSpaceModelDialog extends StatelessWidget {
|
||||
child: TextField(
|
||||
controller: spaceNameController,
|
||||
onChanged: (value) {
|
||||
context
|
||||
.read<CreateSpaceModelBloc>()
|
||||
.add(UpdateSpaceTemplateName(name: value));
|
||||
context.read<CreateSpaceModelBloc>().add(
|
||||
UpdateSpaceTemplateName(
|
||||
name: value,
|
||||
allModels: otherSpaceModels ?? []));
|
||||
},
|
||||
style: const TextStyle(color: ColorsManager.blackColor),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: ColorsManager.blackColor),
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: ColorsManager.textFieldGreyColor,
|
||||
hintText: 'Please enter the name',
|
||||
errorText: state.errorMessage,
|
||||
hintStyle: const TextStyle(
|
||||
color: ColorsManager.lightGrayColor),
|
||||
hintStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(color: ColorsManager.lightGrayColor),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: BorderSide.none,
|
||||
@ -111,7 +128,6 @@ class CreateSpaceModelDialog extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SubspaceModelCreate(
|
||||
context,
|
||||
subspaces: state.space.subspaceModels ?? [],
|
||||
onSpaceModelUpdate: (updatedSubspaces) {
|
||||
context
|
||||
@ -128,21 +144,9 @@ class CreateSpaceModelDialog extends StatelessWidget {
|
||||
subspaces: subspaces,
|
||||
allTags: allTags,
|
||||
spaceNameController: spaceNameController,
|
||||
onLoad: (tags, subspaces) {
|
||||
if (context.read<CreateSpaceModelBloc>().state
|
||||
is CreateSpaceModelLoaded) {
|
||||
if (subspaces != null) {
|
||||
context
|
||||
.read<CreateSpaceModelBloc>()
|
||||
.add(AddSubspacesToSpaceTemplate(subspaces));
|
||||
}
|
||||
if (tags != null) {
|
||||
context
|
||||
.read<CreateSpaceModelBloc>()
|
||||
.add(AddTagsToSpaceTemplate(tags));
|
||||
}
|
||||
}
|
||||
},
|
||||
pageContext: pageContext,
|
||||
otherSpaceModels: otherSpaceModels,
|
||||
allSpaceModels: allSpaceModels,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
SizedBox(
|
||||
@ -152,38 +156,99 @@ class CreateSpaceModelDialog extends StatelessWidget {
|
||||
Expanded(
|
||||
child: CancelButton(
|
||||
label: 'Cancel',
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: DefaultButton(
|
||||
onPressed: state.errorMessage == null ||
|
||||
isNameValid
|
||||
onPressed: ((state.errorMessage != null &&
|
||||
state.errorMessage != '') ||
|
||||
!isNameValid)
|
||||
? () {
|
||||
final updatedSpaceTemplate =
|
||||
updatedSpaceModel.copyWith(
|
||||
modelName:
|
||||
spaceNameController.text.trim(),
|
||||
);
|
||||
context.read<CreateSpaceModelBloc>().add(
|
||||
CreateSpaceTemplate(
|
||||
spaceTemplate:
|
||||
updatedSpaceTemplate,
|
||||
onCreate: (newModel) {
|
||||
onLoad!(newModel);
|
||||
Navigator.of(context)
|
||||
.pop(); // Close the dialog
|
||||
},
|
||||
),
|
||||
);
|
||||
if (updatedSpaceModel.uuid == null) {
|
||||
context
|
||||
.read<CreateSpaceModelBloc>()
|
||||
.add(
|
||||
CreateSpaceTemplate(
|
||||
spaceTemplate:
|
||||
updatedSpaceTemplate,
|
||||
onCreate: (newModel) {
|
||||
if (pageContext != null) {
|
||||
pageContext!
|
||||
.read<SpaceModelBloc>()
|
||||
.add(CreateSpaceModel(
|
||||
newSpaceModel:
|
||||
newModel));
|
||||
}
|
||||
Navigator.of(context)
|
||||
.pop(); // Close the dialog
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
if (pageContext != null) {
|
||||
final currentState = pageContext!
|
||||
.read<SpaceModelBloc>()
|
||||
.state;
|
||||
if (currentState
|
||||
is SpaceModelLoaded) {
|
||||
final spaceModels =
|
||||
List<SpaceTemplateModel>.from(
|
||||
currentState.spaceModels);
|
||||
|
||||
final SpaceTemplateModel?
|
||||
currentSpaceModel = spaceModels
|
||||
.cast<SpaceTemplateModel?>()
|
||||
.firstWhere(
|
||||
(sm) =>
|
||||
sm?.uuid ==
|
||||
updatedSpaceModel
|
||||
.uuid,
|
||||
orElse: () => null,
|
||||
);
|
||||
if (currentSpaceModel != null) {
|
||||
context
|
||||
.read<CreateSpaceModelBloc>()
|
||||
.add(ModifySpaceTemplate(
|
||||
spaceTemplate:
|
||||
currentSpaceModel,
|
||||
updatedSpaceTemplate:
|
||||
updatedSpaceTemplate,
|
||||
onUpdate: (newModel) {
|
||||
if (pageContext !=
|
||||
null) {
|
||||
pageContext!
|
||||
.read<
|
||||
SpaceModelBloc>()
|
||||
.add(UpdateSpaceModel(
|
||||
spaceModelUuid:
|
||||
newModel.uuid ??
|
||||
''));
|
||||
}
|
||||
Navigator.of(context)
|
||||
.pop();
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
: null,
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
borderRadius: 10,
|
||||
foregroundColor: isNameValid
|
||||
? ColorsManager.whiteColors
|
||||
: ColorsManager.whiteColorsWithOpacity,
|
||||
foregroundColor: ((state.errorMessage != null &&
|
||||
state.errorMessage != '') ||
|
||||
!isNameValid)
|
||||
? ColorsManager.whiteColorsWithOpacity
|
||||
: ColorsManager.whiteColors,
|
||||
child: const Text('OK'),
|
||||
),
|
||||
),
|
||||
@ -195,7 +260,10 @@ class CreateSpaceModelDialog extends StatelessWidget {
|
||||
} else if (state is CreateSpaceModelError) {
|
||||
return Text(
|
||||
'Error: ${state.message}',
|
||||
style: const TextStyle(color: ColorsManager.warningRed),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(color: ColorsManager.warningRed),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class SubspaceChipWidget extends StatelessWidget {
|
||||
final String subspace;
|
||||
|
||||
|
@ -1,23 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/common/edit_chip.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/button_content_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class SubspaceModelCreate extends StatelessWidget {
|
||||
class SubspaceModelCreate extends StatefulWidget {
|
||||
final List<SubspaceTemplateModel> subspaces;
|
||||
final void Function(List<SubspaceTemplateModel> newSubspaces)?
|
||||
onSpaceModelUpdate;
|
||||
|
||||
const SubspaceModelCreate(BuildContext context,
|
||||
{Key? key, required this.subspaces, this.onSpaceModelUpdate})
|
||||
: super(key: key);
|
||||
const SubspaceModelCreate({
|
||||
Key? key,
|
||||
required this.subspaces,
|
||||
this.onSpaceModelUpdate,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_SubspaceModelCreateState createState() => _SubspaceModelCreateState();
|
||||
}
|
||||
|
||||
class _SubspaceModelCreateState extends State<SubspaceModelCreate> {
|
||||
late List<SubspaceTemplateModel> _subspaces;
|
||||
String? errorSubspaceId;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_subspaces = List.from(widget.subspaces);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
return Container(
|
||||
child: subspaces.isEmpty
|
||||
child: _subspaces.isEmpty
|
||||
? TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
overlayColor: ColorsManager.transparentColor,
|
||||
@ -39,46 +57,37 @@ class SubspaceModelCreate extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
width: 3.0, // Border width
|
||||
width: 3.0,
|
||||
),
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
...subspaces.map(
|
||||
(subspace) => Chip(
|
||||
label: Text(
|
||||
subspace.subspaceName,
|
||||
style: const TextStyle(
|
||||
color: ColorsManager.spaceColor), // Text color
|
||||
),
|
||||
backgroundColor:
|
||||
ColorsManager.whiteColors, // Chip background color
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(16), // Rounded chip
|
||||
side: const BorderSide(
|
||||
color: ColorsManager.spaceColor), // Border color
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
..._subspaces.map((subspace) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SubspaceNameDisplayWidget(
|
||||
text: subspace.subspaceName,
|
||||
validateName: (updatedName) {
|
||||
return !_subspaces.any((s) =>
|
||||
s != subspace &&
|
||||
s.subspaceName == updatedName);
|
||||
},
|
||||
onNameChanged: (updatedName) {
|
||||
setState(() {
|
||||
subspace.subspaceName = updatedName;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
EditChip(
|
||||
onTap: () async {
|
||||
await _openDialog(context, 'Edit Sub-space');
|
||||
},
|
||||
child: Chip(
|
||||
label: const Text(
|
||||
'Edit',
|
||||
style: TextStyle(color: ColorsManager.spaceColor),
|
||||
),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side:
|
||||
const BorderSide(color: ColorsManager.spaceColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -95,9 +104,15 @@ class SubspaceModelCreate extends StatelessWidget {
|
||||
return CreateSubSpaceModelDialog(
|
||||
isEdit: true,
|
||||
dialogTitle: dialogTitle,
|
||||
existingSubSpaces: subspaces,
|
||||
existingSubSpaces: _subspaces,
|
||||
onUpdate: (subspaceModels) {
|
||||
onSpaceModelUpdate!(subspaceModels);
|
||||
setState(() {
|
||||
_subspaces = subspaceModels;
|
||||
errorSubspaceId = null;
|
||||
});
|
||||
if (widget.onSpaceModelUpdate != null) {
|
||||
widget.onSpaceModelUpdate!(subspaceModels);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
|
@ -0,0 +1,130 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class SubspaceNameDisplayWidget extends StatefulWidget {
|
||||
final String text;
|
||||
final TextStyle? textStyle;
|
||||
final Color backgroundColor;
|
||||
final Color borderColor;
|
||||
final EdgeInsetsGeometry padding;
|
||||
final BorderRadiusGeometry borderRadius;
|
||||
final void Function(String updatedName) onNameChanged;
|
||||
final bool Function(String updatedName) validateName;
|
||||
|
||||
const SubspaceNameDisplayWidget({
|
||||
Key? key,
|
||||
required this.text,
|
||||
this.textStyle,
|
||||
this.backgroundColor = ColorsManager.whiteColors,
|
||||
this.borderColor = ColorsManager.transparentColor,
|
||||
this.padding = const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
||||
this.borderRadius = const BorderRadius.all(Radius.circular(10)),
|
||||
required this.onNameChanged,
|
||||
required this.validateName,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_SubspaceNameDisplayWidgetState createState() =>
|
||||
_SubspaceNameDisplayWidgetState();
|
||||
}
|
||||
|
||||
class _SubspaceNameDisplayWidgetState extends State<SubspaceNameDisplayWidget> {
|
||||
bool isEditing = false;
|
||||
late TextEditingController _controller;
|
||||
late FocusNode _focusNode;
|
||||
late String previousName;
|
||||
String? errorText;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = TextEditingController(text: widget.text);
|
||||
_focusNode = FocusNode();
|
||||
previousName = widget.text;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _handleValidationAndSave() {
|
||||
final updatedName = _controller.text;
|
||||
if (widget.validateName(updatedName)) {
|
||||
setState(() {
|
||||
errorText = null;
|
||||
isEditing = false;
|
||||
previousName = updatedName;
|
||||
widget.onNameChanged(updatedName);
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
errorText = 'Subspace name already exists.';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
isEditing = true;
|
||||
_focusNode.requestFocus();
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
padding: widget.padding,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.backgroundColor,
|
||||
borderRadius: widget.borderRadius,
|
||||
border: Border.all(color: widget.borderColor),
|
||||
),
|
||||
child: isEditing
|
||||
? TextField(
|
||||
controller: _controller,
|
||||
focusNode: _focusNode,
|
||||
style: widget.textStyle ??
|
||||
Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(color: ColorsManager.spaceColor),
|
||||
autofocus: true,
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 8.0),
|
||||
),
|
||||
onSubmitted: (value) {
|
||||
_handleValidationAndSave();
|
||||
},
|
||||
)
|
||||
: Text(
|
||||
widget.text,
|
||||
style: widget.textStyle ??
|
||||
Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(color: ColorsManager.spaceColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (errorText != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: Text(
|
||||
errorText!,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(color: ColorsManager.warningRed),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/common/edit_chip.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
@ -17,8 +17,9 @@ class TagChipDisplay extends StatelessWidget {
|
||||
final List<SubspaceTemplateModel>? subspaces;
|
||||
final List<String>? allTags;
|
||||
final TextEditingController spaceNameController;
|
||||
final void Function(
|
||||
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces)? onLoad;
|
||||
final BuildContext? pageContext;
|
||||
final List<String>? otherSpaceModels;
|
||||
final List<SpaceTemplateModel>? allSpaceModels;
|
||||
|
||||
const TagChipDisplay(BuildContext context,
|
||||
{Key? key,
|
||||
@ -28,7 +29,9 @@ class TagChipDisplay extends StatelessWidget {
|
||||
required this.subspaces,
|
||||
required this.allTags,
|
||||
required this.spaceNameController,
|
||||
this.onLoad})
|
||||
this.pageContext,
|
||||
this.otherSpaceModels,
|
||||
this.allSpaceModels})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
@ -70,9 +73,12 @@ class TagChipDisplay extends StatelessWidget {
|
||||
),
|
||||
label: Text(
|
||||
'x${entry.value}', // Show count
|
||||
style: const TextStyle(
|
||||
color: ColorsManager.spaceColor,
|
||||
),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(
|
||||
color:
|
||||
ColorsManager.spaceColor),
|
||||
),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
shape: RoundedRectangleBorder(
|
||||
@ -83,20 +89,21 @@ class TagChipDisplay extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
// Use the Navigator's context for showDialog
|
||||
final navigatorContext =
|
||||
Navigator.of(context).overlay?.context;
|
||||
EditChip(onTap: () async {
|
||||
// Use the Navigator's context for showDialog
|
||||
Navigator.of(context).pop();
|
||||
|
||||
if (navigatorContext != null) {
|
||||
await showDialog<bool>(
|
||||
barrierDismissible: false,
|
||||
context: navigatorContext,
|
||||
builder: (context) => AssignTagModelsDialog(
|
||||
await showDialog<bool>(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (context) => AssignTagModelsDialog(
|
||||
products: products,
|
||||
allSpaceModels: allSpaceModels,
|
||||
subspaces: subspaces,
|
||||
pageContext: pageContext,
|
||||
allTags: allTags,
|
||||
spaceModel: spaceModel,
|
||||
otherSpaceModels: otherSpaceModels,
|
||||
initialTags: TagHelper.generateInitialTags(
|
||||
subspaces: subspaces,
|
||||
spaceTagModels: spaceModel?.tags ?? []),
|
||||
@ -105,30 +112,16 @@ class TagChipDisplay extends StatelessWidget {
|
||||
TagHelper.createInitialSelectedProducts(
|
||||
spaceModel?.tags ?? [], subspaces),
|
||||
spaceName: spaceModel?.modelName ?? '',
|
||||
onUpdate: (tags, subspaces) {
|
||||
onLoad?.call(tags, subspaces);
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Chip(
|
||||
label: const Text(
|
||||
'Edit',
|
||||
style: TextStyle(color: ColorsManager.spaceColor),
|
||||
),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
side: const BorderSide(color: ColorsManager.spaceColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
})
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
await showDialog<bool>(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
@ -137,13 +130,10 @@ class TagChipDisplay extends StatelessWidget {
|
||||
subspaces: subspaces,
|
||||
allTags: allTags,
|
||||
spaceName: spaceNameController.text,
|
||||
pageContext: pageContext,
|
||||
isCreate: true,
|
||||
onUpdate: (tags, subspaces) {
|
||||
onLoad?.call(tags, subspaces);
|
||||
},
|
||||
onLoad: (tags, subspaces) {
|
||||
onLoad?.call(tags, subspaces);
|
||||
},
|
||||
spaceModel: spaceModel,
|
||||
otherSpaceModels: otherSpaceModels,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -5,8 +5,11 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_state.dart';
|
||||
import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart';
|
||||
@ -21,28 +24,27 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
|
||||
final List<String>? allTags;
|
||||
final String spaceName;
|
||||
final bool isCreate;
|
||||
final void Function(
|
||||
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces)? onLoad;
|
||||
final void Function(
|
||||
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces)? onUpdate;
|
||||
final List<String>? otherSpaceModels;
|
||||
final BuildContext? pageContext;
|
||||
final SpaceTemplateModel? spaceModel;
|
||||
final List<SpaceTemplateModel>? allSpaceModels;
|
||||
|
||||
const AddDeviceTypeModelWidget({
|
||||
super.key,
|
||||
this.products,
|
||||
this.initialSelectedProducts,
|
||||
this.subspaces,
|
||||
this.allTags,
|
||||
this.spaceTagModels,
|
||||
required this.spaceName,
|
||||
required this.isCreate,
|
||||
this.onLoad,
|
||||
this.onUpdate,
|
||||
});
|
||||
const AddDeviceTypeModelWidget(
|
||||
{super.key,
|
||||
this.products,
|
||||
this.initialSelectedProducts,
|
||||
this.subspaces,
|
||||
this.allTags,
|
||||
this.spaceTagModels,
|
||||
required this.spaceName,
|
||||
required this.isCreate,
|
||||
this.pageContext,
|
||||
this.otherSpaceModels,
|
||||
this.spaceModel,
|
||||
this.allSpaceModels});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
|
||||
final size = MediaQuery.of(context).size;
|
||||
final crossAxisCount = size.width > 1200
|
||||
? 8
|
||||
@ -79,6 +81,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
child: ScrollableGridViewWidget(
|
||||
isCreate: isCreate,
|
||||
products: products,
|
||||
crossAxisCount: crossAxisCount,
|
||||
initialProductCounts: state.selectedProducts,
|
||||
@ -102,6 +105,45 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
|
||||
onPressed: () async {
|
||||
if (isCreate) {
|
||||
Navigator.of(context).pop();
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext dialogContext) {
|
||||
return CreateSpaceModelDialog(
|
||||
allSpaceModels: allSpaceModels,
|
||||
products: products,
|
||||
allTags: allTags,
|
||||
pageContext: pageContext,
|
||||
otherSpaceModels: otherSpaceModels,
|
||||
spaceModel: SpaceTemplateModel(
|
||||
modelName: spaceName,
|
||||
tags: spaceModel?.tags ?? [],
|
||||
uuid: spaceModel?.uuid,
|
||||
internalId: spaceModel?.internalId,
|
||||
subspaceModels: subspaces),
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
final initialTags = TagHelper.generateInitialTags(
|
||||
spaceTagModels: spaceTagModels,
|
||||
subspaces: subspaces,
|
||||
);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AssignTagModelsDialog(
|
||||
products: products,
|
||||
subspaces: subspaces,
|
||||
addedProducts: initialSelectedProducts ?? [],
|
||||
allTags: allTags,
|
||||
spaceName: spaceName,
|
||||
initialTags: initialTags,
|
||||
otherSpaceModels: otherSpaceModels,
|
||||
title: 'Edit Device',
|
||||
spaceModel: spaceModel,
|
||||
pageContext: pageContext,
|
||||
));
|
||||
}
|
||||
},
|
||||
),
|
||||
@ -124,7 +166,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
|
||||
: () async {
|
||||
if (state is AddDeviceModelLoaded &&
|
||||
state.selectedProducts.isNotEmpty) {
|
||||
final initialTags = generateInitialTags(
|
||||
final initialTags =
|
||||
TagHelper.generateInitialTags(
|
||||
spaceTagModels: spaceTagModels,
|
||||
subspaces: subspaces,
|
||||
);
|
||||
@ -137,15 +180,16 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
|
||||
context: context,
|
||||
builder: (context) => AssignTagModelsDialog(
|
||||
products: products,
|
||||
allSpaceModels: allSpaceModels,
|
||||
subspaces: subspaces,
|
||||
addedProducts: state.selectedProducts,
|
||||
allTags: allTags,
|
||||
spaceName: spaceName,
|
||||
initialTags: state.initialTag,
|
||||
initialTags: initialTags,
|
||||
otherSpaceModels: otherSpaceModels,
|
||||
title: dialogTitle,
|
||||
onUpdate: (tags, subspaces) {
|
||||
onUpdate?.call(tags, subspaces);
|
||||
},
|
||||
spaceModel: spaceModel,
|
||||
pageContext: pageContext,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -162,29 +206,4 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<TagModel> generateInitialTags({
|
||||
List<TagModel>? spaceTagModels,
|
||||
List<SubspaceTemplateModel>? subspaces,
|
||||
}) {
|
||||
final List<TagModel> initialTags = [];
|
||||
|
||||
if (spaceTagModels != null) {
|
||||
initialTags.addAll(spaceTagModels);
|
||||
}
|
||||
|
||||
if (subspaces != null) {
|
||||
for (var subspace in subspaces) {
|
||||
if (subspace.tags != null) {
|
||||
initialTags.addAll(
|
||||
subspace.tags!.map(
|
||||
(tag) => tag.copyWith(location: subspace.subspaceName),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return initialTags;
|
||||
}
|
||||
}
|
||||
|
@ -13,12 +13,13 @@ import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
class DeviceTypeTileWidget extends StatelessWidget {
|
||||
final ProductModel product;
|
||||
final List<SelectedProduct> productCounts;
|
||||
final bool isCreate;
|
||||
|
||||
const DeviceTypeTileWidget({
|
||||
super.key,
|
||||
required this.product,
|
||||
required this.productCounts,
|
||||
});
|
||||
const DeviceTypeTileWidget(
|
||||
{super.key,
|
||||
required this.product,
|
||||
required this.productCounts,
|
||||
required this.isCreate});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -48,6 +49,7 @@ class DeviceTypeTileWidget extends StatelessWidget {
|
||||
DeviceNameWidget(name: product.name),
|
||||
const SizedBox(height: 4),
|
||||
CounterWidget(
|
||||
isCreate: isCreate,
|
||||
initialCount: selectedProduct.count,
|
||||
onCountChanged: (newCount) {
|
||||
context.read<AddDeviceTypeModelBloc>().add(
|
||||
|
@ -10,12 +10,14 @@ class ScrollableGridViewWidget extends StatelessWidget {
|
||||
final List<ProductModel>? products;
|
||||
final int crossAxisCount;
|
||||
final List<SelectedProduct>? initialProductCounts;
|
||||
final bool isCreate;
|
||||
|
||||
const ScrollableGridViewWidget({
|
||||
super.key,
|
||||
required this.products,
|
||||
required this.crossAxisCount,
|
||||
this.initialProductCounts,
|
||||
required this.isCreate
|
||||
});
|
||||
|
||||
@override
|
||||
@ -30,7 +32,7 @@ class ScrollableGridViewWidget extends StatelessWidget {
|
||||
final productCounts = state is AddDeviceModelLoaded
|
||||
? state.selectedProducts
|
||||
: <SelectedProduct>[];
|
||||
|
||||
|
||||
return GridView.builder(
|
||||
controller: scrollController,
|
||||
shrinkWrap: true,
|
||||
@ -47,6 +49,7 @@ class ScrollableGridViewWidget extends StatelessWidget {
|
||||
|
||||
return DeviceTypeTileWidget(
|
||||
product: product,
|
||||
isCreate: isCreate,
|
||||
productCounts: initialProductCount != null
|
||||
? [...productCounts, initialProductCount]
|
||||
: productCounts,
|
||||
|
@ -34,6 +34,20 @@ class SpaceModelManagementApi {
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
Future<String?> updateSpaceModel(
|
||||
CreateSpaceTemplateBodyModel spaceModel, String spaceModelUuid) async {
|
||||
final response = await HTTPService().put(
|
||||
path: ApiEndpoints.updateSpaceModel
|
||||
.replaceAll('{projectId}', TempConst.projectId).replaceAll('{spaceModelUuid}', spaceModelUuid),
|
||||
body: spaceModel.toJson(),
|
||||
expectedResponseModel: (json) {
|
||||
return json['message'];
|
||||
},
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<SpaceTemplateModel?> getSpaceModel(String spaceModelUuid) async {
|
||||
final response = await HTTPService().get(
|
||||
path: ApiEndpoints.getSpaceModel
|
||||
|
@ -71,8 +71,8 @@ class UserPermissionApi {
|
||||
"firstName": firstName,
|
||||
"lastName": lastName,
|
||||
"email": email,
|
||||
"jobTitle": jobTitle != '' ? jobTitle : " ",
|
||||
"phoneNumber": phoneNumber != '' ? phoneNumber : " ",
|
||||
"jobTitle": jobTitle != '' ? jobTitle : null,
|
||||
"phoneNumber": phoneNumber != '' ? phoneNumber : null,
|
||||
"roleUuid": roleUuid,
|
||||
"projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c",
|
||||
"spaceUuids": spaceUuids,
|
||||
@ -119,13 +119,8 @@ class UserPermissionApi {
|
||||
);
|
||||
return response ?? 'Unknown error occurred';
|
||||
} on DioException catch (e) {
|
||||
if (e.response != null) {
|
||||
final errorMessage = e.response?.data['error'];
|
||||
return errorMessage is String
|
||||
? errorMessage
|
||||
: 'Error occurred while checking email';
|
||||
}
|
||||
return 'Error occurred while checking email';
|
||||
final errorMessage = e.response?.data['error'];
|
||||
return errorMessage;
|
||||
} catch (e) {
|
||||
return e.toString();
|
||||
}
|
||||
|
@ -70,5 +70,5 @@ abstract class ColorsManager {
|
||||
static const Color invitedOrangeText = Color(0xFFFFBF00);
|
||||
static const Color lightGrayBorderColor = Color(0xB2D5D5D5);
|
||||
//background: #F8F8F8;
|
||||
|
||||
static const Color vividBlue = Color(0xFF023DFE);
|
||||
}
|
||||
|
@ -101,8 +101,11 @@ abstract class ApiEndpoints {
|
||||
//space model
|
||||
static const String listSpaceModels = '/projects/{projectId}/space-models';
|
||||
static const String createSpaceModel = '/projects/{projectId}/space-models';
|
||||
static const String getSpaceModel = '/projects/{projectId}/space-models/{spaceModelUuid}';
|
||||
|
||||
static const String getSpaceModel =
|
||||
'/projects/{projectId}/space-models/{spaceModelUuid}';
|
||||
static const String updateSpaceModel =
|
||||
'/projects/{projectId}/space-models/{spaceModelUuid}';
|
||||
|
||||
static const String roleTypes = '/role/types';
|
||||
static const String permission = '/permission/{roleUuid}';
|
||||
static const String inviteUser = '/invite-user';
|
||||
|
@ -259,6 +259,7 @@ class Assets {
|
||||
|
||||
static const String delete = 'assets/icons/delete.svg';
|
||||
static const String edit = 'assets/icons/edit.svg';
|
||||
static const String editSpace = 'assets/icons/edit_space.svg';
|
||||
//assets/icons/routine/tab_to_run.svg
|
||||
static const String tabToRun = 'assets/icons/routine/tab_to_run.svg';
|
||||
|
||||
@ -398,5 +399,7 @@ class Assets {
|
||||
static const String ZtoAIcon = 'assets/icons/ztoa_icon.png';
|
||||
static const String AtoZIcon = 'assets/icons/atoz_icon.png';
|
||||
static const String link = 'assets/icons/link.svg';
|
||||
static const String duplicate = 'assets/icons/duplicate.svg';
|
||||
static const String spaceDelete = 'assets/icons/space_delete.svg';
|
||||
}
|
||||
//user_management.svg
|
||||
|
Reference in New Issue
Block a user