mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-11-27 09:14:55 +00:00
Compare commits
57 Commits
feat/space
...
chore/remo
| Author | SHA1 | Date | |
|---|---|---|---|
| c5871be990 | |||
| 97bdb1bbb7 | |||
| 7ce0a27af0 | |||
| bc4af6a237 | |||
| 830725254f | |||
| ba7db3a5fb | |||
| 513175ed1e | |||
| f35b699d4c | |||
| 7ffdc67016 | |||
| 18afc4f563 | |||
| 44d95f5701 | |||
| e47f3d6d59 | |||
| 788ea27de1 | |||
| 81e9e58627 | |||
| 25eae3dfaa | |||
| 0e912207e5 | |||
| eb53671e3a | |||
| 2f6bd31aa2 | |||
| fe680d15f2 | |||
| 440263e2f9 | |||
| ec5b7d4395 | |||
| 7109421358 | |||
| 145086b9de | |||
| bae5ae17a7 | |||
| 9706c2655c | |||
| 8a95f93556 | |||
| 60028cdf78 | |||
| c12c73f20a | |||
| a7256c8d5d | |||
| 5975adb5e2 | |||
| 0bb24604bc | |||
| cf2690123e | |||
| a220483310 | |||
| 12df07e681 | |||
| 59eafc99a5 | |||
| db7eaa53af | |||
| 20a9f19480 | |||
| a4e7f30411 | |||
| 210fbf7497 | |||
| acbb6ca7c0 | |||
| eb7eeebf18 | |||
| 15023e5882 | |||
| 408c40aa60 | |||
| 2abe7a6feb | |||
| a381fd317d | |||
| a588351482 | |||
| 1be52adcc8 | |||
| 3c5e0a7778 | |||
| 6591ef1664 | |||
| cfc1b544b7 | |||
| 15640ff0df | |||
| bfbc32d51b | |||
| 8aa493a15e | |||
| e70df16de3 | |||
| a7e7554813 | |||
| 1ab8c8341d | |||
| 67516817ec |
12
assets/icons/link.svg
Normal file
12
assets/icons/link.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="Group 440">
|
||||||
|
<path id="Vector" d="M6.7986 0.892983L4.84001 2.85148C4.48185 3.20973 4.23182 3.63572 4.08964 4.08854C3.62464 4.23503 3.19865 4.49284 2.85148 4.84001L0.892983 6.7986C-0.29757 7.98915 -0.297753 9.91625 0.892983 11.107C2.08354 12.2976 4.01072 12.2977 5.20146 11.107L7.15996 9.14848C7.5182 8.79033 7.76814 8.36433 7.91032 7.91142C8.37541 7.76503 8.80132 7.50713 9.14848 7.15996L11.107 5.20146C12.2976 4.01081 12.2978 2.08372 11.107 0.892983C9.91643 -0.29757 7.98933 -0.297753 6.7986 0.892983ZM4.17735 6.1656C4.32576 6.5276 4.54658 6.86653 4.84001 7.15996C5.1251 7.44496 5.46431 7.67027 5.83418 7.82289L3.87577 9.78139C3.41893 10.2381 2.67552 10.2382 2.21867 9.78139C1.76182 9.32445 1.76182 8.58113 2.21867 8.12428L4.17717 6.16569C4.17726 6.16569 4.17726 6.16569 4.17735 6.1656ZM6.82854 8.81706L4.86995 10.7756C3.86259 11.783 2.23194 11.7831 1.2244 10.7756C0.216957 9.76821 0.216866 8.13747 1.2244 7.13002L3.1829 5.17143C3.41105 4.94328 3.67939 4.76082 3.9719 4.63255C3.92823 4.9897 3.94819 5.34263 4.02427 5.67991C3.96128 5.72697 3.90159 5.77843 3.84574 5.83427L1.88725 7.79277C1.24766 8.43245 1.24766 9.47313 1.88725 10.1127C2.52683 10.7523 3.56752 10.7523 4.2072 10.1127L6.16569 8.15422C6.80592 7.5139 6.80601 6.47459 6.16569 5.83427C5.82365 5.49223 5.74034 4.99492 5.90358 4.57753C6.24891 4.70598 6.56587 4.90876 6.82854 5.17143C7.8336 6.1765 7.83369 7.8119 6.82854 8.81706ZM10.7756 4.86995L8.81706 6.82854C8.58891 7.05669 8.32057 7.23915 8.02806 7.36742C8.07173 7.01027 8.05177 6.65733 7.97578 6.32005C8.03868 6.27299 8.09846 6.22154 8.15422 6.16569L10.1128 4.2072C10.7524 3.56761 10.7524 2.52683 10.1128 1.88725C9.5385 1.31303 8.64028 1.25379 7.99913 1.71229C7.89384 1.78755 7.86958 1.93394 7.94484 2.03922C8.0201 2.14451 8.16649 2.16886 8.27177 2.09352C8.73952 1.75907 9.37434 1.81162 9.78139 2.21867C10.2382 2.67552 10.2382 3.41883 9.78139 3.87577L7.8228 5.83427C7.8228 5.83427 7.8228 5.83427 7.82271 5.83436C7.67421 5.47236 7.45338 5.13344 7.15996 4.84001C6.87495 4.555 6.53566 4.32969 6.16578 4.17707L6.96431 3.37855C7.05577 3.28709 7.05577 3.13868 6.96431 3.04713C6.87276 2.95567 6.72444 2.95567 6.63289 3.04713L5.83427 3.84574C5.19404 4.48597 5.19395 5.52528 5.83427 6.16569C6.17631 6.50764 6.25963 7.00505 6.09639 7.42244C5.75105 7.29399 5.43409 7.0912 5.17143 6.82844C4.16645 5.82347 4.16636 4.18797 5.17143 3.1829L7.13002 1.2244C8.13737 0.216957 9.76811 0.216774 10.7756 1.2244C11.783 2.23176 11.7831 3.8625 10.7756 4.86995Z" fill="#023DFE" fill-opacity="0.7"/>
|
||||||
|
<path id="Vector_2" d="M7.69571 2.55103C7.69571 2.68048 7.59079 2.7854 7.46143 2.7854C7.33197 2.7854 7.22705 2.68048 7.22705 2.55103C7.22705 2.42157 7.33197 2.31665 7.46143 2.31665C7.59079 2.31665 7.69571 2.42157 7.69571 2.55103Z" fill="#023DFE" fill-opacity="0.7"/>
|
||||||
|
<path id="Vector_3" d="M3.18286 3.1831C3.27441 3.09164 3.27441 2.94323 3.18286 2.85168L2.18859 1.85741C2.09704 1.76595 1.94872 1.76595 1.85717 1.85741C1.76571 1.94897 1.76571 2.09737 1.85717 2.18893L2.85143 3.18319C2.94308 3.27465 3.09139 3.27465 3.18286 3.1831Z" fill="#023DFE" fill-opacity="0.7"/>
|
||||||
|
<path id="Vector_4" d="M0.891022 3.9375C0.761658 3.9375 0.656738 4.04242 0.656738 4.17178C0.656738 4.30124 0.761658 4.40616 0.891022 4.40616H2.29718C2.42655 4.40616 2.53147 4.30124 2.53147 4.17178C2.53147 4.04242 2.42655 3.9375 2.29718 3.9375H0.891022Z" fill="#023DFE" fill-opacity="0.7"/>
|
||||||
|
<path id="Vector_5" d="M3.93774 0.821289V2.22736C3.93774 2.35672 4.04266 2.46173 4.17203 2.46173C4.30148 2.46173 4.4064 2.35672 4.4064 2.22736V0.821289C4.4064 0.691834 4.30148 0.586914 4.17203 0.586914C4.04266 0.586914 3.93774 0.691834 3.93774 0.821289Z" fill="#023DFE" fill-opacity="0.7"/>
|
||||||
|
<path id="Vector_6" d="M8.8172 8.81738C8.72565 8.90884 8.72565 9.05724 8.8172 9.1488L9.81146 10.1431C9.85724 10.1888 9.91721 10.2117 9.97717 10.2117C10.184 10.2117 10.291 9.95986 10.1429 9.81164L9.14862 8.81738C9.05707 8.72591 8.90875 8.72591 8.8172 8.81738Z" fill="#023DFE" fill-opacity="0.7"/>
|
||||||
|
<path id="Vector_7" d="M8.03882 11.1785V9.77237C8.03882 9.64301 7.93381 9.53809 7.80444 9.53809C7.67499 9.53809 7.57007 9.64301 7.57007 9.77237V11.1785C7.57007 11.3079 7.67499 11.4128 7.80444 11.4128C7.93381 11.4128 8.03882 11.3079 8.03882 11.1785Z" fill="#023DFE" fill-opacity="0.7"/>
|
||||||
|
<path id="Vector_8" d="M11.1793 8.03906C11.3086 8.03906 11.4135 7.93405 11.4135 7.80469C11.4135 7.67523 11.3086 7.57031 11.1793 7.57031H9.7731C9.64374 7.57031 9.53882 7.67523 9.53882 7.80469C9.53882 7.93405 9.64374 8.03906 9.7731 8.03906H11.1793Z" fill="#023DFE" fill-opacity="0.7"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.5 KiB |
138
lib/common/dialog_dropdown.dart
Normal file
138
lib/common/dialog_dropdown.dart
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class DialogDropdown extends StatefulWidget {
|
||||||
|
final List<String> items;
|
||||||
|
final ValueChanged<String> onSelected;
|
||||||
|
final String? selectedValue;
|
||||||
|
|
||||||
|
const DialogDropdown({
|
||||||
|
Key? key,
|
||||||
|
required this.items,
|
||||||
|
required this.onSelected,
|
||||||
|
this.selectedValue,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_DialogDropdownState createState() => _DialogDropdownState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DialogDropdownState extends State<DialogDropdown> {
|
||||||
|
bool _isOpen = false;
|
||||||
|
late OverlayEntry _overlayEntry;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _toggleDropdown() {
|
||||||
|
if (_isOpen) {
|
||||||
|
_closeDropdown();
|
||||||
|
} else {
|
||||||
|
_openDropdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _openDropdown() {
|
||||||
|
_overlayEntry = _createOverlayEntry();
|
||||||
|
Overlay.of(context).insert(_overlayEntry);
|
||||||
|
_isOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _closeDropdown() {
|
||||||
|
_overlayEntry.remove();
|
||||||
|
_isOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OverlayEntry _createOverlayEntry() {
|
||||||
|
final renderBox = context.findRenderObject() as RenderBox;
|
||||||
|
final size = renderBox.size;
|
||||||
|
final offset = renderBox.localToGlobal(Offset.zero);
|
||||||
|
|
||||||
|
return OverlayEntry(
|
||||||
|
builder: (context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
_closeDropdown();
|
||||||
|
},
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned(
|
||||||
|
left: offset.dx,
|
||||||
|
top: offset.dy + size.height,
|
||||||
|
width: size.width,
|
||||||
|
child: Material(
|
||||||
|
elevation: 4.0,
|
||||||
|
child: Container(
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxHeight: 200.0, // Set max height for dropdown
|
||||||
|
),
|
||||||
|
child: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: widget.items.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = widget.items[index];
|
||||||
|
return Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: ColorsManager.lightGrayBorderColor,
|
||||||
|
width: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: ListTile(
|
||||||
|
title: Text(
|
||||||
|
item,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium
|
||||||
|
?.copyWith(
|
||||||
|
color: ColorsManager.textPrimaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
widget.onSelected(item);
|
||||||
|
_closeDropdown();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: _toggleDropdown,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: ColorsManager.transparentColor),
|
||||||
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.selectedValue ?? 'Select an item',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
const Icon(Icons.arrow_drop_down),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
160
lib/common/dialog_textfield_dropdown.dart
Normal file
160
lib/common/dialog_textfield_dropdown.dart
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class DialogTextfieldDropdown extends StatefulWidget {
|
||||||
|
final List<String> items;
|
||||||
|
final ValueChanged<String> onSelected;
|
||||||
|
final String? initialValue;
|
||||||
|
|
||||||
|
const DialogTextfieldDropdown({
|
||||||
|
Key? key,
|
||||||
|
required this.items,
|
||||||
|
required this.onSelected,
|
||||||
|
this.initialValue,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_DialogTextfieldDropdownState createState() =>
|
||||||
|
_DialogTextfieldDropdownState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
|
||||||
|
bool _isOpen = false;
|
||||||
|
late OverlayEntry _overlayEntry;
|
||||||
|
final TextEditingController _controller = TextEditingController();
|
||||||
|
late List<String> _filteredItems; // Filtered items list
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller.text = widget.initialValue ?? 'Select Tag';
|
||||||
|
_filteredItems = List.from(widget.items); // Initialize filtered items
|
||||||
|
}
|
||||||
|
|
||||||
|
void _toggleDropdown() {
|
||||||
|
if (_isOpen) {
|
||||||
|
_closeDropdown();
|
||||||
|
} else {
|
||||||
|
_openDropdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _openDropdown() {
|
||||||
|
_overlayEntry = _createOverlayEntry();
|
||||||
|
Overlay.of(context).insert(_overlayEntry);
|
||||||
|
_isOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _closeDropdown() {
|
||||||
|
_overlayEntry.remove();
|
||||||
|
_isOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OverlayEntry _createOverlayEntry() {
|
||||||
|
final renderBox = context.findRenderObject() as RenderBox;
|
||||||
|
final size = renderBox.size;
|
||||||
|
final offset = renderBox.localToGlobal(Offset.zero);
|
||||||
|
|
||||||
|
return OverlayEntry(
|
||||||
|
builder: (context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
_closeDropdown();
|
||||||
|
},
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned(
|
||||||
|
left: offset.dx,
|
||||||
|
top: offset.dy + size.height,
|
||||||
|
width: size.width,
|
||||||
|
child: Material(
|
||||||
|
elevation: 4.0,
|
||||||
|
child: Container(
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxHeight: 200.0,
|
||||||
|
),
|
||||||
|
child: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: _filteredItems.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = _filteredItems[index];
|
||||||
|
return Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: ColorsManager.lightGrayBorderColor,
|
||||||
|
width: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: ListTile(
|
||||||
|
title: Text(item,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium
|
||||||
|
?.copyWith(
|
||||||
|
color: ColorsManager.textPrimaryColor)),
|
||||||
|
onTap: () {
|
||||||
|
_controller.text = item;
|
||||||
|
widget.onSelected(item);
|
||||||
|
setState(() {
|
||||||
|
_filteredItems
|
||||||
|
.remove(item); // Remove selected item
|
||||||
|
});
|
||||||
|
_closeDropdown();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: _toggleDropdown,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: ColorsManager.transparentColor),
|
||||||
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _controller,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_filteredItems = widget.items
|
||||||
|
.where((item) =>
|
||||||
|
item.toLowerCase().contains(value.toLowerCase()))
|
||||||
|
.toList(); // Filter items dynamically
|
||||||
|
});
|
||||||
|
widget.onSelected(value);
|
||||||
|
},
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: 'Enter or Select tag',
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Icon(Icons.arrow_drop_down),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,6 +10,10 @@ class UserModel {
|
|||||||
final String? phoneNumber;
|
final String? phoneNumber;
|
||||||
final bool? isEmailVerified;
|
final bool? isEmailVerified;
|
||||||
final bool? isAgreementAccepted;
|
final bool? isAgreementAccepted;
|
||||||
|
final bool? hasAcceptedWebAgreement;
|
||||||
|
final DateTime? webAgreementAcceptedAt;
|
||||||
|
final UserRole? role;
|
||||||
|
|
||||||
UserModel({
|
UserModel({
|
||||||
required this.uuid,
|
required this.uuid,
|
||||||
required this.email,
|
required this.email,
|
||||||
@ -19,6 +23,9 @@ class UserModel {
|
|||||||
required this.phoneNumber,
|
required this.phoneNumber,
|
||||||
required this.isEmailVerified,
|
required this.isEmailVerified,
|
||||||
required this.isAgreementAccepted,
|
required this.isAgreementAccepted,
|
||||||
|
required this.hasAcceptedWebAgreement,
|
||||||
|
required this.webAgreementAcceptedAt,
|
||||||
|
required this.role,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory UserModel.fromJson(Map<String, dynamic> json) {
|
factory UserModel.fromJson(Map<String, dynamic> json) {
|
||||||
@ -31,6 +38,11 @@ class UserModel {
|
|||||||
phoneNumber: json['phoneNumber'],
|
phoneNumber: json['phoneNumber'],
|
||||||
isEmailVerified: json['isEmailVerified'],
|
isEmailVerified: json['isEmailVerified'],
|
||||||
isAgreementAccepted: json['isAgreementAccepted'],
|
isAgreementAccepted: json['isAgreementAccepted'],
|
||||||
|
hasAcceptedWebAgreement: json['hasAcceptedWebAgreement'],
|
||||||
|
webAgreementAcceptedAt: json['webAgreementAcceptedAt'] != null
|
||||||
|
? DateTime.parse(json['webAgreementAcceptedAt'])
|
||||||
|
: null,
|
||||||
|
role: json['role'] != null ? UserRole.fromJson(json['role']) : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +53,9 @@ class UserModel {
|
|||||||
Map<String, dynamic> tempJson = Token.decodeToken(token.accessToken);
|
Map<String, dynamic> tempJson = Token.decodeToken(token.accessToken);
|
||||||
|
|
||||||
return UserModel(
|
return UserModel(
|
||||||
|
hasAcceptedWebAgreement: null,
|
||||||
|
role: null,
|
||||||
|
webAgreementAcceptedAt: null,
|
||||||
uuid: tempJson['uuid'].toString(),
|
uuid: tempJson['uuid'].toString(),
|
||||||
email: tempJson['email'],
|
email: tempJson['email'],
|
||||||
firstName: null,
|
firstName: null,
|
||||||
@ -65,3 +80,26 @@ class UserModel {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UserRole {
|
||||||
|
final String uuid;
|
||||||
|
final DateTime createdAt;
|
||||||
|
final DateTime updatedAt;
|
||||||
|
final String type;
|
||||||
|
|
||||||
|
UserRole({
|
||||||
|
required this.uuid,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
required this.type,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory UserRole.fromJson(Map<String, dynamic> json) {
|
||||||
|
return UserRole(
|
||||||
|
uuid: json['uuid'],
|
||||||
|
createdAt: DateTime.parse(json['createdAt']),
|
||||||
|
updatedAt: DateTime.parse(json['updatedAt']),
|
||||||
|
type: json['type'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -18,10 +18,15 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
|||||||
List<Node> sourcesList = [];
|
List<Node> sourcesList = [];
|
||||||
List<Node> destinationsList = [];
|
List<Node> destinationsList = [];
|
||||||
UserModel? user;
|
UserModel? user;
|
||||||
|
String terms = '';
|
||||||
|
String policy = '';
|
||||||
|
|
||||||
HomeBloc() : super((HomeInitial())) {
|
HomeBloc() : super((HomeInitial())) {
|
||||||
on<CreateNewNode>(_createNode);
|
on<CreateNewNode>(_createNode);
|
||||||
on<FetchUserInfo>(_fetchUserInfo);
|
on<FetchUserInfo>(_fetchUserInfo);
|
||||||
|
on<FetchTermEvent>(_fetchTerms);
|
||||||
|
on<FetchPolicyEvent>(_fetchPolicy);
|
||||||
|
on<ConfirmUserAgreementEvent>(_confirmUserAgreement);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _createNode(CreateNewNode event, Emitter<HomeState> emit) async {
|
void _createNode(CreateNewNode event, Emitter<HomeState> emit) async {
|
||||||
@ -45,12 +50,45 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
|||||||
var uuid =
|
var uuid =
|
||||||
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
|
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
|
||||||
user = await HomeApi().fetchUserInfo(uuid);
|
user = await HomeApi().fetchUserInfo(uuid);
|
||||||
|
add(FetchTermEvent());
|
||||||
emit(HomeInitial());
|
emit(HomeInitial());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future _fetchTerms(FetchTermEvent event, Emitter<HomeState> emit) async {
|
||||||
|
try {
|
||||||
|
emit(LoadingHome());
|
||||||
|
terms = await HomeApi().fetchTerms();
|
||||||
|
add(FetchPolicyEvent());
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _fetchPolicy(FetchPolicyEvent event, Emitter<HomeState> emit) async {
|
||||||
|
try {
|
||||||
|
emit(LoadingHome());
|
||||||
|
policy = await HomeApi().fetchPolicy();
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _confirmUserAgreement(
|
||||||
|
ConfirmUserAgreementEvent event, Emitter<HomeState> emit) async {
|
||||||
|
try {
|
||||||
|
emit(LoadingHome());
|
||||||
|
var uuid =
|
||||||
|
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
|
||||||
|
policy = await HomeApi().confirmUserAgreements(uuid);
|
||||||
|
emit(PolicyAgreement());
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// static Future fetchUserInfo() async {
|
// static Future fetchUserInfo() async {
|
||||||
// try {
|
// try {
|
||||||
// var uuid =
|
// var uuid =
|
||||||
|
|||||||
@ -20,4 +20,8 @@ class CreateNewNode extends HomeEvent {
|
|||||||
|
|
||||||
class FetchUserInfo extends HomeEvent {
|
class FetchUserInfo extends HomeEvent {
|
||||||
const FetchUserInfo();
|
const FetchUserInfo();
|
||||||
}
|
}class FetchTermEvent extends HomeEvent {}
|
||||||
|
|
||||||
|
class FetchPolicyEvent extends HomeEvent {}
|
||||||
|
|
||||||
|
class ConfirmUserAgreementEvent extends HomeEvent {}
|
||||||
@ -7,8 +7,12 @@ abstract class HomeState extends Equatable {
|
|||||||
@override
|
@override
|
||||||
List<Object> get props => [];
|
List<Object> get props => [];
|
||||||
}
|
}
|
||||||
|
class LoadingHome extends HomeState {}
|
||||||
|
|
||||||
class HomeInitial extends HomeState {}
|
class HomeInitial extends HomeState {}
|
||||||
|
class TermsAgreement extends HomeState {}
|
||||||
|
|
||||||
|
class PolicyAgreement extends HomeState {}
|
||||||
|
|
||||||
class HomeCounterState extends HomeState {
|
class HomeCounterState extends HomeState {
|
||||||
final int counter;
|
final int counter;
|
||||||
@ -24,3 +28,5 @@ class HomeUpdateTree extends HomeState {
|
|||||||
@override
|
@override
|
||||||
List<Object> get props => [graph, builder];
|
List<Object> get props => [graph, builder];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//FetchTermEvent
|
||||||
176
lib/pages/home/view/agreement_and_privacy_dialog.dart
Normal file
176
lib/pages/home/view/agreement_and_privacy_dialog.dart
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_html/flutter_html.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/constants/routes_const.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
class AgreementAndPrivacyDialog extends StatefulWidget {
|
||||||
|
final String terms;
|
||||||
|
final String policy;
|
||||||
|
|
||||||
|
const AgreementAndPrivacyDialog({
|
||||||
|
super.key,
|
||||||
|
required this.terms,
|
||||||
|
required this.policy,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_AgreementAndPrivacyDialogState createState() =>
|
||||||
|
_AgreementAndPrivacyDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AgreementAndPrivacyDialogState extends State<AgreementAndPrivacyDialog> {
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
bool _isAtEnd = false;
|
||||||
|
int _currentPage = 1;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_scrollController.addListener(_onScroll);
|
||||||
|
WidgetsBinding.instance
|
||||||
|
.addPostFrameCallback((_) => _checkScrollRequirement());
|
||||||
|
}
|
||||||
|
|
||||||
|
void _checkScrollRequirement() {
|
||||||
|
final scrollPosition = _scrollController.position;
|
||||||
|
if (scrollPosition.maxScrollExtent <= 0) {
|
||||||
|
setState(() {
|
||||||
|
_isAtEnd = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.removeListener(_onScroll);
|
||||||
|
_scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onScroll() {
|
||||||
|
if (_scrollController.position.atEdge) {
|
||||||
|
final isAtBottom = _scrollController.position.pixels ==
|
||||||
|
_scrollController.position.maxScrollExtent;
|
||||||
|
if (isAtBottom && !_isAtEnd) {
|
||||||
|
setState(() {
|
||||||
|
_isAtEnd = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String get _dialogTitle =>
|
||||||
|
_currentPage == 2 ? 'User Agreement' : 'Privacy Policy';
|
||||||
|
|
||||||
|
String get _dialogContent => _currentPage == 2 ? widget.terms : widget.policy;
|
||||||
|
|
||||||
|
Widget _buildScrollableContent() {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(40),
|
||||||
|
width: MediaQuery.of(context).size.width * 0.8,
|
||||||
|
height: MediaQuery.of(context).size.height * 0.75,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[200],
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||||
|
),
|
||||||
|
child: Scrollbar(
|
||||||
|
thumbVisibility: true,
|
||||||
|
trackVisibility: true,
|
||||||
|
interactive: true,
|
||||||
|
controller: _scrollController,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
controller: _scrollController,
|
||||||
|
padding: const EdgeInsets.all(25),
|
||||||
|
child: Html(
|
||||||
|
data: _dialogContent,
|
||||||
|
onLinkTap: (url, attributes, element) async {
|
||||||
|
if (url != null) {
|
||||||
|
final uri = Uri.parse(url);
|
||||||
|
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
"body": Style(
|
||||||
|
fontSize: FontSize(14),
|
||||||
|
color: Colors.black87,
|
||||||
|
lineHeight: LineHeight(1.5),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildActionButton() {
|
||||||
|
final String buttonText = _currentPage == 2 ? "I Agree" : "Next";
|
||||||
|
|
||||||
|
return InkWell(
|
||||||
|
onTap: _isAtEnd
|
||||||
|
? () {
|
||||||
|
if (_currentPage == 1) {
|
||||||
|
setState(() {
|
||||||
|
_currentPage = 2;
|
||||||
|
_isAtEnd = false;
|
||||||
|
_scrollController.jumpTo(0);
|
||||||
|
WidgetsBinding.instance
|
||||||
|
.addPostFrameCallback((_) => _checkScrollRequirement());
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
child: Text(
|
||||||
|
buttonText,
|
||||||
|
style: TextStyle(
|
||||||
|
color: _isAtEnd ? ColorsManager.secondaryColor : Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Text(
|
||||||
|
_dialogTitle,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: ColorsManager.secondaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
_buildScrollableContent(),
|
||||||
|
const Divider(),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
AuthBloc.logout();
|
||||||
|
context.go(RoutesConst.auth);
|
||||||
|
},
|
||||||
|
child: const Text("Cancel"),
|
||||||
|
),
|
||||||
|
_buildActionButton(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
||||||
|
import 'package:syncrow_web/pages/home/view/agreement_and_privacy_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/home/bloc/home_state.dart';
|
import 'package:syncrow_web/pages/home/bloc/home_state.dart';
|
||||||
import 'package:syncrow_web/pages/home/view/home_card.dart';
|
import 'package:syncrow_web/pages/home/view/home_card.dart';
|
||||||
@ -9,16 +11,40 @@ import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
|||||||
|
|
||||||
class HomeWebPage extends StatelessWidget {
|
class HomeWebPage extends StatelessWidget {
|
||||||
const HomeWebPage({super.key});
|
const HomeWebPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Size size = MediaQuery.of(context).size;
|
Size size = MediaQuery.of(context).size;
|
||||||
|
final homeBloc = BlocProvider.of<HomeBloc>(context);
|
||||||
|
|
||||||
return PopScope(
|
return PopScope(
|
||||||
canPop: false,
|
canPop: false,
|
||||||
onPopInvoked: (didPop) => false,
|
onPopInvoked: (didPop) => false,
|
||||||
child: BlocConsumer<HomeBloc, HomeState>(
|
child: BlocConsumer<HomeBloc, HomeState>(
|
||||||
listener: (BuildContext context, state) {},
|
listener: (BuildContext context, state) {
|
||||||
|
if (state is HomeInitial) {
|
||||||
|
if (homeBloc.user!.hasAcceptedWebAgreement == false) {
|
||||||
|
Future.delayed(const Duration(seconds: 1), () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AgreementAndPrivacyDialog(
|
||||||
|
terms: homeBloc.terms,
|
||||||
|
policy: homeBloc.policy,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
).then((v) {
|
||||||
|
if (v != null) {
|
||||||
|
homeBloc.add(ConfirmUserAgreementEvent());
|
||||||
|
homeBloc.add(const FetchUserInfo());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final homeBloc = BlocProvider.of<HomeBloc>(context);
|
|
||||||
return WebScaffold(
|
return WebScaffold(
|
||||||
enableMenuSidebar: false,
|
enableMenuSidebar: false,
|
||||||
appBarTitle: Row(
|
appBarTitle: Row(
|
||||||
@ -52,7 +78,8 @@ class HomeWebPage extends StatelessWidget {
|
|||||||
width: size.width * 0.68,
|
width: size.width * 0.68,
|
||||||
child: GridView.builder(
|
child: GridView.builder(
|
||||||
itemCount: 3, //8
|
itemCount: 3, //8
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate:
|
||||||
|
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
crossAxisCount: 4,
|
crossAxisCount: 4,
|
||||||
crossAxisSpacing: 20.0,
|
crossAxisSpacing: 20.0,
|
||||||
mainAxisSpacing: 20.0,
|
mainAxisSpacing: 20.0,
|
||||||
@ -64,7 +91,8 @@ class HomeWebPage extends StatelessWidget {
|
|||||||
active: homeBloc.homeItems[index].active!,
|
active: homeBloc.homeItems[index].active!,
|
||||||
name: homeBloc.homeItems[index].title!,
|
name: homeBloc.homeItems[index].title!,
|
||||||
img: homeBloc.homeItems[index].icon!,
|
img: homeBloc.homeItems[index].icon!,
|
||||||
onTap: () => homeBloc.homeItems[index].onPress(context),
|
onTap: () =>
|
||||||
|
homeBloc.homeItems[index].onPress(context),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@ -42,7 +42,7 @@ class RolesUserModel {
|
|||||||
invitedBy:
|
invitedBy:
|
||||||
json['invitedBy'].toString().toLowerCase().replaceAll("_", " "),
|
json['invitedBy'].toString().toLowerCase().replaceAll("_", " "),
|
||||||
phoneNumber: json['phoneNumber'],
|
phoneNumber: json['phoneNumber'],
|
||||||
jobTitle: json['jobTitle'].toString(),
|
jobTitle: json['jobTitle'] ?? "-",
|
||||||
createdDate: json['createdDate'],
|
createdDate: json['createdDate'],
|
||||||
createdTime: json['createdTime'],
|
createdTime: json['createdTime'],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -114,7 +114,7 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
|
|||||||
currentStep++;
|
currentStep++;
|
||||||
if (currentStep == 2) {
|
if (currentStep == 2) {
|
||||||
_blocRole.add(
|
_blocRole.add(
|
||||||
CheckStepStatus(isEditUser: false));
|
const CheckStepStatus(isEditUser: false));
|
||||||
} else if (currentStep == 3) {
|
} else if (currentStep == 3) {
|
||||||
_blocRole
|
_blocRole
|
||||||
.add(const CheckSpacesStepStatus());
|
.add(const CheckSpacesStepStatus());
|
||||||
@ -151,11 +151,11 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
|
|||||||
Widget _getFormContent() {
|
Widget _getFormContent() {
|
||||||
switch (currentStep) {
|
switch (currentStep) {
|
||||||
case 1:
|
case 1:
|
||||||
return BasicsView(
|
return const BasicsView(
|
||||||
userId: '',
|
userId: '',
|
||||||
);
|
);
|
||||||
case 2:
|
case 2:
|
||||||
return SpacesAccessView();
|
return const SpacesAccessView();
|
||||||
case 3:
|
case 3:
|
||||||
return const RolesAndPermission();
|
return const RolesAndPermission();
|
||||||
default:
|
default:
|
||||||
@ -172,7 +172,7 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
|
|||||||
bloc.add(const CheckSpacesStepStatus());
|
bloc.add(const CheckSpacesStepStatus());
|
||||||
currentStep = step;
|
currentStep = step;
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
bloc.add(ValidateBasicsStep());
|
bloc.add(const ValidateBasicsStep());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -237,10 +237,11 @@ class _AddNewUserDialogState extends State<AddNewUserDialog> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
currentStep = step;
|
currentStep = step;
|
||||||
bloc.add(CheckStepStatus(isEditUser: false));
|
bloc.add(const CheckStepStatus(isEditUser: false));
|
||||||
if (step3 == 3) {
|
if (step3 == 3) {
|
||||||
bloc.add(const CheckRoleStepStatus());
|
bloc.add(const CheckRoleStepStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Column(
|
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/country_picker_dialog.dart';
|
||||||
import 'package:intl_phone_field/intl_phone_field.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_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/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/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
@ -47,7 +46,9 @@ class BasicsView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.18,
|
||||||
|
height: MediaQuery.of(context).size.width * 0.08,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -76,12 +77,12 @@ class BasicsView extends StatelessWidget {
|
|||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
style:
|
style:
|
||||||
const TextStyle(color: ColorsManager.blackColor),
|
const TextStyle(color: ColorsManager.blackColor),
|
||||||
onChanged: (value) {
|
// onChanged: (value) {
|
||||||
Future.delayed(const Duration(milliseconds: 200),
|
// Future.delayed(const Duration(milliseconds: 200),
|
||||||
() {
|
// () {
|
||||||
_blocRole.add(ValidateBasicsStep());
|
// _blocRole.add(const ValidateBasicsStep());
|
||||||
});
|
// });
|
||||||
},
|
// },
|
||||||
controller: _blocRole.firstNameController,
|
controller: _blocRole.firstNameController,
|
||||||
decoration: inputTextFormDeco(
|
decoration: inputTextFormDeco(
|
||||||
hintText: "Enter first name",
|
hintText: "Enter first name",
|
||||||
@ -103,7 +104,9 @@ class BasicsView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.18,
|
||||||
|
height: MediaQuery.of(context).size.width * 0.08,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -128,12 +131,12 @@ class BasicsView extends StatelessWidget {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
onChanged: (value) {
|
// onChanged: (value) {
|
||||||
Future.delayed(const Duration(milliseconds: 200),
|
// Future.delayed(const Duration(milliseconds: 200),
|
||||||
() {
|
// () {
|
||||||
_blocRole.add(ValidateBasicsStep());
|
// _blocRole.add(ValidateBasicsStep());
|
||||||
});
|
// });
|
||||||
},
|
// },
|
||||||
controller: _blocRole.lastNameController,
|
controller: _blocRole.lastNameController,
|
||||||
style: const TextStyle(color: Colors.black),
|
style: const TextStyle(color: Colors.black),
|
||||||
decoration:
|
decoration:
|
||||||
@ -186,13 +189,13 @@ class BasicsView extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
enabled: userId != '' ? false : true,
|
enabled: userId != '' ? false : true,
|
||||||
onChanged: (value) {
|
// onChanged: (value) {
|
||||||
Future.delayed(const Duration(milliseconds: 200), () {
|
// Future.delayed(const Duration(milliseconds: 200), () {
|
||||||
_blocRole.add(CheckStepStatus(
|
// _blocRole.add(CheckStepStatus(
|
||||||
isEditUser: userId != '' ? false : true));
|
// isEditUser: userId != '' ? false : true));
|
||||||
_blocRole.add(ValidateBasicsStep());
|
// _blocRole.add(ValidateBasicsStep());
|
||||||
});
|
// });
|
||||||
},
|
// },
|
||||||
controller: _blocRole.emailController,
|
controller: _blocRole.emailController,
|
||||||
style: const TextStyle(color: ColorsManager.blackColor),
|
style: const TextStyle(color: ColorsManager.blackColor),
|
||||||
decoration: inputTextFormDeco(hintText: "name@example.com")
|
decoration: inputTextFormDeco(hintText: "name@example.com")
|
||||||
|
|||||||
@ -11,7 +11,14 @@ class DeleteUserDialog extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _DeleteUserDialogState extends State<DeleteUserDialog> {
|
class _DeleteUserDialogState extends State<DeleteUserDialog> {
|
||||||
int currentStep = 1;
|
bool isLoading = false;
|
||||||
|
bool _isDisposed = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_isDisposed = true;
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -56,7 +63,7 @@ class _DeleteUserDialogState extends State<DeleteUserDialog> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).pop(true);
|
Navigator.of(context).pop(false); // Return false if canceled
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
@ -76,7 +83,26 @@ class _DeleteUserDialogState extends State<DeleteUserDialog> {
|
|||||||
)),
|
)),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: InkWell(
|
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(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
@ -91,8 +117,17 @@ class _DeleteUserDialogState extends State<DeleteUserDialog> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: const Center(
|
child: Center(
|
||||||
child: Text(
|
child: isLoading
|
||||||
|
? const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
color: ColorsManager.red,
|
||||||
|
strokeWidth: 2.0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Text(
|
||||||
'Delete',
|
'Delete',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: ColorsManager.red,
|
color: ColorsManager.red,
|
||||||
|
|||||||
@ -128,7 +128,7 @@ class _PermissionManagementState extends State<PermissionManagement> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
option.title,
|
' ${option.title.isNotEmpty ? option.title[0].toUpperCase() : ''}${option.title.substring(1)}',
|
||||||
style: context.textTheme.bodyMedium?.copyWith(
|
style: context.textTheme.bodyMedium?.copyWith(
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
@ -184,7 +184,7 @@ class _PermissionManagementState extends State<PermissionManagement> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
subOption.title,
|
' ${subOption.title.isNotEmpty ? subOption.title[0].toUpperCase() : ''}${subOption.title.substring(1)}',
|
||||||
style: context.textTheme.bodyMedium?.copyWith(
|
style: context.textTheme.bodyMedium?.copyWith(
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
@ -246,7 +246,7 @@ class _PermissionManagementState extends State<PermissionManagement> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
child.title,
|
' ${child.title.isNotEmpty ? child.title[0].toUpperCase() : ''}${child.title.substring(1)}',
|
||||||
style: context.textTheme.bodyMedium?.copyWith(
|
style: context.textTheme.bodyMedium?.copyWith(
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
|||||||
@ -5,94 +5,91 @@ import 'package:syncrow_web/utils/style.dart';
|
|||||||
|
|
||||||
Future<void> showPopUpFilterMenu({
|
Future<void> showPopUpFilterMenu({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
Function()? onSortAtoZ,
|
required Function(String value) onSortAtoZ,
|
||||||
Function()? onSortZtoA,
|
required Function(String value) onSortZtoA,
|
||||||
Function()? cancelButton,
|
Function()? cancelButton,
|
||||||
required Map<String, bool> checkboxStates,
|
required Map<String, bool> checkboxStates,
|
||||||
required RelativeRect position,
|
required RelativeRect position,
|
||||||
Function()? onOkPressed,
|
Function()? onOkPressed,
|
||||||
List<String>? list,
|
List<String>? list,
|
||||||
|
String? isSelected,
|
||||||
}) async {
|
}) async {
|
||||||
|
|
||||||
|
|
||||||
await showMenu(
|
await showMenu(
|
||||||
context: context,
|
context: context,
|
||||||
position: position,
|
position: position,
|
||||||
|
|
||||||
|
|
||||||
color: ColorsManager.whiteColors,
|
color: ColorsManager.whiteColors,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||||
),
|
),
|
||||||
items: <PopupMenuEntry>[
|
items: <PopupMenuEntry>[
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
onTap: onSortAtoZ,
|
enabled: false,
|
||||||
|
child: StatefulBuilder(
|
||||||
|
builder: (context, setState) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
if (isSelected == 'Asc') {
|
||||||
|
isSelected = null;
|
||||||
|
onSortAtoZ.call('');
|
||||||
|
} else {
|
||||||
|
onSortAtoZ.call('Asc');
|
||||||
|
isSelected = 'Asc';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
leading: Image.asset(
|
leading: Image.asset(
|
||||||
Assets.AtoZIcon,
|
Assets.AtoZIcon,
|
||||||
width: 25,
|
width: 25,
|
||||||
),
|
),
|
||||||
title: const Text(
|
title: Text(
|
||||||
"Sort A to Z",
|
"Sort A to Z",
|
||||||
style: TextStyle(color: Colors.blueGrey),
|
style: TextStyle(
|
||||||
|
color: isSelected == "Asc"
|
||||||
|
? ColorsManager.blackColor
|
||||||
|
: ColorsManager.grayColor),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PopupMenuItem(
|
ListTile(
|
||||||
onTap: onSortZtoA,
|
onTap: () {
|
||||||
child: ListTile(
|
setState(() {
|
||||||
|
if (isSelected == 'Desc') {
|
||||||
|
isSelected = null;
|
||||||
|
onSortZtoA.call('');
|
||||||
|
} else {
|
||||||
|
onSortZtoA.call('Desc');
|
||||||
|
isSelected = 'Desc';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
leading: Image.asset(
|
leading: Image.asset(
|
||||||
Assets.ZtoAIcon,
|
Assets.ZtoAIcon,
|
||||||
width: 25,
|
width: 25,
|
||||||
),
|
),
|
||||||
title: const Text(
|
title: Text(
|
||||||
"Sort Z to A",
|
"Sort Z to A",
|
||||||
style: TextStyle(color: Colors.blueGrey),
|
style: TextStyle(
|
||||||
|
color: isSelected == "Desc"
|
||||||
|
? ColorsManager.blackColor
|
||||||
|
: ColorsManager.grayColor),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const Divider(),
|
||||||
const PopupMenuDivider(),
|
const Text(
|
||||||
const PopupMenuItem(
|
|
||||||
child: Text(
|
|
||||||
"Filter by Status",
|
"Filter by Status",
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
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(
|
Container(
|
||||||
child: Container(
|
|
||||||
decoration: containerDecoration.copyWith(
|
decoration: containerDecoration.copyWith(
|
||||||
boxShadow: [],
|
boxShadow: [],
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(10),
|
||||||
|
topRight: Radius.circular(10),
|
||||||
bottomLeft: Radius.circular(10),
|
bottomLeft: Radius.circular(10),
|
||||||
bottomRight: Radius.circular(10))),
|
bottomRight: Radius.circular(10))),
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
@ -105,22 +102,33 @@ Future<void> showPopUpFilterMenu({
|
|||||||
itemCount: list?.length ?? 0,
|
itemCount: list?.length ?? 0,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final item = list![index];
|
final item = list![index];
|
||||||
return CheckboxListTile(
|
return Row(
|
||||||
dense: true,
|
children: [
|
||||||
title: Text(item),
|
Checkbox(
|
||||||
value: checkboxStates[item],
|
value: checkboxStates[item],
|
||||||
onChanged: (bool? newValue) {
|
onChanged: (bool? newValue) {
|
||||||
checkboxStates[item] = newValue ?? false;
|
checkboxStates[item] = newValue ?? false;
|
||||||
(context as Element).markNeedsBuild();
|
(context as Element).markNeedsBuild();
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
item,
|
||||||
|
style: TextStyle(color: ColorsManager.grayColor),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
),
|
),
|
||||||
PopupMenuItem(
|
const Divider(),
|
||||||
child: Row(
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
@ -141,6 +149,13 @@ Future<void> showPopUpFilterMenu({
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -52,14 +52,16 @@ class _RoleDropdownState extends State<RoleDropdown> {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
child: DropdownButtonFormField<String>(
|
child: DropdownButtonFormField<String>(
|
||||||
dropdownColor: ColorsManager.whiteColors,
|
dropdownColor: ColorsManager.whiteColors,
|
||||||
alignment: Alignment.center,
|
// alignment: Alignment.,
|
||||||
focusColor: Colors.white,
|
focusColor: Colors.white,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
value: selectedRole.isNotEmpty ? selectedRole : null,
|
value: selectedRole.isNotEmpty ? selectedRole : null,
|
||||||
items: widget.bloc!.roles.map((role) {
|
items: widget.bloc!.roles.map((role) {
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
value: role.uuid,
|
value: role.uuid,
|
||||||
child: Text(role.type),
|
child: Text(
|
||||||
|
' ${role.type.isNotEmpty ? role.type[0].toUpperCase() : ''}${role.type.substring(1)}',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
|
|||||||
@ -93,7 +93,7 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
|
|||||||
try {
|
try {
|
||||||
emit(UsersLoadingState());
|
emit(UsersLoadingState());
|
||||||
bool res = await UserPermissionApi().changeUserStatusById(
|
bool res = await UserPermissionApi().changeUserStatusById(
|
||||||
event.userId, event.newStatus == "disabled" ? true : false);
|
event.userId, event.newStatus == "disabled" ? false : true);
|
||||||
if (res == true) {
|
if (res == true) {
|
||||||
add(const GetUsers());
|
add(const GetUsers());
|
||||||
// users = users.map((user) {
|
// users = users.map((user) {
|
||||||
@ -133,7 +133,10 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
|
|||||||
} else {
|
} else {
|
||||||
emit(UsersLoadingState());
|
emit(UsersLoadingState());
|
||||||
currentSortOrder = "Asc";
|
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));
|
emit(UsersLoadedState(users: users));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,6 +167,7 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
|
|||||||
emit(UsersLoadedState(users: users));
|
emit(UsersLoadedState(users: users));
|
||||||
} else {
|
} else {
|
||||||
emit(UsersLoadingState());
|
emit(UsersLoadingState());
|
||||||
|
currentSortOrder = "NewestToOldest";
|
||||||
users.sort((a, b) {
|
users.sort((a, b) {
|
||||||
final dateA = _parseDateTime(a.createdDate);
|
final dateA = _parseDateTime(a.createdDate);
|
||||||
final dateB = _parseDateTime(b.createdDate);
|
final dateB = _parseDateTime(b.createdDate);
|
||||||
@ -188,6 +192,7 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
|
|||||||
final dateB = _parseDateTime(b.createdDate);
|
final dateB = _parseDateTime(b.createdDate);
|
||||||
return dateA.compareTo(dateB);
|
return dateA.compareTo(dateB);
|
||||||
});
|
});
|
||||||
|
currentSortOrder = "OldestToNewest";
|
||||||
emit(UsersLoadedState(users: users));
|
emit(UsersLoadedState(users: users));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -256,49 +261,96 @@ class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
|
|||||||
|
|
||||||
void _filterUsersByRole(
|
void _filterUsersByRole(
|
||||||
FilterUsersByRoleEvent event, Emitter<UserTableState> emit) {
|
FilterUsersByRoleEvent event, Emitter<UserTableState> emit) {
|
||||||
selectedRoles = event.selectedRoles.toSet();
|
selectedRoles = event.selectedRoles!.toSet();
|
||||||
|
|
||||||
final filteredUsers = initialUsers.where((user) {
|
final filteredUsers = initialUsers.where((user) {
|
||||||
if (selectedRoles.isEmpty) return true;
|
if (selectedRoles.isEmpty) return true;
|
||||||
return selectedRoles.contains(user.roleType);
|
return selectedRoles.contains(user.roleType);
|
||||||
}).toList();
|
}).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));
|
emit(UsersLoadedState(users: filteredUsers));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _filterUsersByJobTitle(
|
void _filterUsersByJobTitle(
|
||||||
FilterUsersByJobEvent event, Emitter<UserTableState> emit) {
|
FilterUsersByJobEvent event, Emitter<UserTableState> emit) {
|
||||||
selectedJobTitles = event.selectedJob.toSet();
|
selectedJobTitles = event.selectedJob!.toSet();
|
||||||
|
emit(UsersLoadingState());
|
||||||
final filteredUsers = initialUsers.where((user) {
|
final filteredUsers = initialUsers.where((user) {
|
||||||
if (selectedJobTitles.isEmpty) return true;
|
if (selectedJobTitles.isEmpty) return true;
|
||||||
return selectedJobTitles.contains(user.jobTitle);
|
return selectedJobTitles.contains(user.jobTitle);
|
||||||
}).toList();
|
}).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));
|
emit(UsersLoadedState(users: filteredUsers));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _filterUsersByCreated(
|
void _filterUsersByCreated(
|
||||||
FilterUsersByCreatedEvent event, Emitter<UserTableState> emit) {
|
FilterUsersByCreatedEvent event, Emitter<UserTableState> emit) {
|
||||||
selectedCreatedBy = event.selectedCreatedBy.toSet();
|
selectedCreatedBy = event.selectedCreatedBy!.toSet();
|
||||||
|
|
||||||
final filteredUsers = initialUsers.where((user) {
|
final filteredUsers = initialUsers.where((user) {
|
||||||
if (selectedCreatedBy.isEmpty) return true;
|
if (selectedCreatedBy.isEmpty) return true;
|
||||||
return selectedCreatedBy.contains(user.invitedBy);
|
return selectedCreatedBy.contains(user.invitedBy);
|
||||||
}).toList();
|
}).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));
|
emit(UsersLoadedState(users: filteredUsers));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _filterUserStatus(
|
void _filterUserStatus(
|
||||||
FilterUsersByDeActevateEvent event, Emitter<UserTableState> emit) {
|
FilterUsersByDeActevateEvent event, Emitter<UserTableState> emit) {
|
||||||
selectedStatuses = event.selectedActivate.toSet();
|
selectedStatuses = event.selectedActivate!.toSet();
|
||||||
|
|
||||||
final filteredUsers = initialUsers.where((user) {
|
final filteredUsers = initialUsers.where((user) {
|
||||||
if (selectedStatuses.isEmpty) return true;
|
if (selectedStatuses.isEmpty) return true;
|
||||||
return selectedStatuses.contains(user.status);
|
return selectedStatuses.contains(user.status);
|
||||||
}).toList();
|
}).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));
|
emit(UsersLoadedState(users: filteredUsers));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -89,35 +89,36 @@ class DeleteUserEvent extends UserTableEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class FilterUsersByRoleEvent extends UserTableEvent {
|
class FilterUsersByRoleEvent extends UserTableEvent {
|
||||||
final List<String> selectedRoles;
|
final List<String>? selectedRoles;
|
||||||
|
final String? sortOrder;
|
||||||
|
|
||||||
FilterUsersByRoleEvent(this.selectedRoles);
|
const FilterUsersByRoleEvent({this.selectedRoles, this.sortOrder});
|
||||||
@override
|
List<Object?> get props => [selectedRoles, sortOrder];
|
||||||
List<Object?> get props => [selectedRoles];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class FilterUsersByJobEvent extends UserTableEvent {
|
class FilterUsersByJobEvent extends UserTableEvent {
|
||||||
final List<String> selectedJob;
|
final List<String>? selectedJob;
|
||||||
|
final String? sortOrder;
|
||||||
|
|
||||||
FilterUsersByJobEvent(this.selectedJob);
|
const FilterUsersByJobEvent({this.selectedJob, this.sortOrder});
|
||||||
@override
|
List<Object?> get props => [selectedJob, sortOrder];
|
||||||
List<Object?> get props => [selectedJob];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class FilterUsersByCreatedEvent extends UserTableEvent {
|
class FilterUsersByCreatedEvent extends UserTableEvent {
|
||||||
final List<String> selectedCreatedBy;
|
final List<String>? selectedCreatedBy;
|
||||||
|
|
||||||
FilterUsersByCreatedEvent(this.selectedCreatedBy);
|
final String? sortOrder;
|
||||||
@override
|
|
||||||
List<Object?> get props => [selectedCreatedBy];
|
const FilterUsersByCreatedEvent({this.selectedCreatedBy, this.sortOrder});
|
||||||
|
List<Object?> get props => [selectedCreatedBy, sortOrder];
|
||||||
}
|
}
|
||||||
|
|
||||||
class FilterUsersByDeActevateEvent extends UserTableEvent {
|
class FilterUsersByDeActevateEvent extends UserTableEvent {
|
||||||
final List<String> selectedActivate;
|
final List<String>? selectedActivate;
|
||||||
|
final String? sortOrder;
|
||||||
|
|
||||||
FilterUsersByDeActevateEvent(this.selectedActivate);
|
const FilterUsersByDeActevateEvent({this.selectedActivate, this.sortOrder});
|
||||||
@override
|
List<Object?> get props => [selectedActivate, sortOrder];
|
||||||
List<Object?> get props => [selectedActivate];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class FilterOptionsEvent extends UserTableEvent {
|
class FilterOptionsEvent extends UserTableEvent {
|
||||||
|
|||||||
@ -19,19 +19,13 @@ class DynamicTableScreen extends StatefulWidget {
|
|||||||
class _DynamicTableScreenState extends State<DynamicTableScreen>
|
class _DynamicTableScreenState extends State<DynamicTableScreen>
|
||||||
with WidgetsBindingObserver {
|
with WidgetsBindingObserver {
|
||||||
late List<double> columnWidths;
|
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.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);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +38,6 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
|
|||||||
@override
|
@override
|
||||||
void didChangeMetrics() {
|
void didChangeMetrics() {
|
||||||
super.didChangeMetrics();
|
super.didChangeMetrics();
|
||||||
// Screen size might have changed
|
|
||||||
final newScreenWidth = MediaQuery.of(context).size.width;
|
final newScreenWidth = MediaQuery.of(context).size.width;
|
||||||
setState(() {
|
setState(() {
|
||||||
columnWidths = List<double>.generate(widget.titles.length, (index) {
|
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
|
0.12; // 20% of screen width for the second column
|
||||||
} else if (index == 9) {
|
} else if (index == 9) {
|
||||||
return newScreenWidth *
|
return newScreenWidth *
|
||||||
0.2; // 25% of screen width for the tenth column
|
0.1; // 25% of screen width for the tenth column
|
||||||
}
|
}
|
||||||
return newScreenWidth *
|
return newScreenWidth *
|
||||||
0.09; // Default to 10% of screen width for other columns
|
0.09; // Default to 10% of screen width for other columns
|
||||||
@ -64,21 +57,18 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
if (columnWidths.every((width) => width == screenWidth * 7)) {
|
||||||
// Initialize column widths if they are still set to placeholder values
|
|
||||||
if (columnWidths.every((width) => width == 120.0)) {
|
|
||||||
columnWidths = List<double>.generate(widget.titles.length, (index) {
|
columnWidths = List<double>.generate(widget.titles.length, (index) {
|
||||||
if (index == 1) {
|
if (index == 1) {
|
||||||
return screenWidth * 0.11;
|
return screenWidth * 0.11;
|
||||||
} else if (index == 9) {
|
} else if (index == 9) {
|
||||||
return screenWidth * 0.2;
|
return screenWidth * 0.1;
|
||||||
}
|
}
|
||||||
return screenWidth * 0.11;
|
return screenWidth * 0.09;
|
||||||
});
|
});
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
return Container(
|
return SingleChildScrollView(
|
||||||
child: SingleChildScrollView(
|
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: Container(
|
child: Container(
|
||||||
@ -88,9 +78,8 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
|
|||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// Header Row with Resizable Columns
|
|
||||||
Container(
|
Container(
|
||||||
width: MediaQuery.of(context).size.width,
|
width: totalWidth,
|
||||||
decoration: containerDecoration.copyWith(
|
decoration: containerDecoration.copyWith(
|
||||||
color: ColorsManager.circleRolesBackground,
|
color: ColorsManager.circleRolesBackground,
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: const BorderRadius.only(
|
||||||
@ -106,8 +95,7 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
|
|||||||
padding: const EdgeInsets.only(left: 5, right: 5),
|
padding: const EdgeInsets.only(left: 5, right: 5),
|
||||||
width: columnWidths[index],
|
width: columnWidths[index],
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment:
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
MainAxisAlignment.spaceBetween,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
@ -146,21 +134,20 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
|
|||||||
GestureDetector(
|
GestureDetector(
|
||||||
onHorizontalDragUpdate: (details) {
|
onHorizontalDragUpdate: (details) {
|
||||||
setState(() {
|
setState(() {
|
||||||
columnWidths[index] = (columnWidths[index] +
|
columnWidths[index] =
|
||||||
details.delta.dx)
|
(columnWidths[index] + details.delta.dx)
|
||||||
.clamp(
|
.clamp(150.0, 300.0);
|
||||||
150.0, 300.0); // Minimum & Maximum size
|
totalWidth = columnWidths.reduce((a, b) => a + b);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: MouseRegion(
|
child: MouseRegion(
|
||||||
cursor: SystemMouseCursors
|
cursor: SystemMouseCursors.resizeColumn,
|
||||||
.resizeColumn, // Set the cursor to resize
|
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.green,
|
color: Colors.green,
|
||||||
child: Container(
|
child: Container(
|
||||||
color: ColorsManager.boxDivider,
|
color: ColorsManager.boxDivider,
|
||||||
width: 1,
|
width: 1,
|
||||||
height: 50, // Height of the header cell
|
height: 50,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -170,12 +157,9 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Data Rows with Dividers
|
|
||||||
widget.rows.isEmpty
|
widget.rows.isEmpty
|
||||||
? Container(
|
? SizedBox(
|
||||||
child: SizedBox(
|
|
||||||
height: MediaQuery.of(context).size.height / 2,
|
height: MediaQuery.of(context).size.height / 2,
|
||||||
child: Container(
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@ -197,13 +181,10 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
: Center(
|
: Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
// height: MediaQuery.of(context).size.height * 0.59,
|
width: totalWidth,
|
||||||
width: MediaQuery.of(context).size.width,
|
|
||||||
decoration: containerDecoration.copyWith(
|
decoration: containerDecoration.copyWith(
|
||||||
color: ColorsManager.whiteColors,
|
color: ColorsManager.whiteColors,
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: const BorderRadius.only(
|
||||||
@ -214,8 +195,7 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
|
|||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: widget.rows.length,
|
itemCount: widget.rows.length,
|
||||||
itemBuilder: (context, rowIndex) {
|
itemBuilder: (context, rowIndex) {
|
||||||
if (columnWidths
|
if (columnWidths.every((width) => width == 120.0)) {
|
||||||
.every((width) => width == 120.0)) {
|
|
||||||
columnWidths = List<double>.generate(
|
columnWidths = List<double>.generate(
|
||||||
widget.titles.length, (index) {
|
widget.titles.length, (index) {
|
||||||
if (index == 1) {
|
if (index == 1) {
|
||||||
@ -233,10 +213,7 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
|
|||||||
Container(
|
Container(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
left: 5,
|
left: 5, top: 10, right: 5, bottom: 10),
|
||||||
top: 10,
|
|
||||||
right: 5,
|
|
||||||
bottom: 10),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
children:
|
children:
|
||||||
List.generate(row.length, (index) {
|
List.generate(row.length, (index) {
|
||||||
@ -278,79 +255,6 @@ class _DynamicTableScreenState extends State<DynamicTableScreen>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 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) {
|
builder: (context, state) {
|
||||||
final screenSize = MediaQuery.of(context).size;
|
final screenSize = MediaQuery.of(context).size;
|
||||||
final _blocRole = BlocProvider.of<UserTableBloc>(context);
|
final _blocRole = BlocProvider.of<UserTableBloc>(context);
|
||||||
|
|
||||||
if (state is UsersLoadingState) {
|
if (state is UsersLoadingState) {
|
||||||
_blocRole.add(ChangePage(_blocRole.currentPage));
|
_blocRole.add(ChangePage(_blocRole.currentPage));
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
@ -189,8 +188,6 @@ class UsersPage extends StatelessWidget {
|
|||||||
const SizedBox(height: 25),
|
const SizedBox(height: 25),
|
||||||
DynamicTableScreen(
|
DynamicTableScreen(
|
||||||
onFilter: (columnIndex) {
|
onFilter: (columnIndex) {
|
||||||
_blocRole.add(FilterClearEvent());
|
|
||||||
|
|
||||||
if (columnIndex == 0) {
|
if (columnIndex == 0) {
|
||||||
showNameMenu(
|
showNameMenu(
|
||||||
context: context,
|
context: context,
|
||||||
@ -210,11 +207,12 @@ class UsersPage extends StatelessWidget {
|
|||||||
if (columnIndex == 2) {
|
if (columnIndex == 2) {
|
||||||
final Map<String, bool> checkboxStates = {
|
final Map<String, bool> checkboxStates = {
|
||||||
for (var item in _blocRole.jobTitle)
|
for (var item in _blocRole.jobTitle)
|
||||||
item: false, // Initialize with false
|
item: _blocRole.selectedJobTitles.contains(item),
|
||||||
};
|
};
|
||||||
final RenderBox overlay = Overlay.of(context)
|
final RenderBox overlay = Overlay.of(context)
|
||||||
.context
|
.context
|
||||||
.findRenderObject() as RenderBox;
|
.findRenderObject() as RenderBox;
|
||||||
|
|
||||||
showPopUpFilterMenu(
|
showPopUpFilterMenu(
|
||||||
position: RelativeRect.fromLTRB(
|
position: RelativeRect.fromLTRB(
|
||||||
overlay.size.width / 4,
|
overlay.size.width / 4,
|
||||||
@ -225,26 +223,28 @@ class UsersPage extends StatelessWidget {
|
|||||||
list: _blocRole.jobTitle,
|
list: _blocRole.jobTitle,
|
||||||
context: context,
|
context: context,
|
||||||
checkboxStates: checkboxStates,
|
checkboxStates: checkboxStates,
|
||||||
|
isSelected: _blocRole.currentSortOrder,
|
||||||
onOkPressed: () {
|
onOkPressed: () {
|
||||||
|
_blocRole.add(FilterClearEvent());
|
||||||
final selectedItems = checkboxStates.entries
|
final selectedItems = checkboxStates.entries
|
||||||
.where((entry) => entry.value)
|
.where((entry) => entry.value)
|
||||||
.map((entry) => entry.key)
|
.map((entry) => entry.key)
|
||||||
.toList();
|
.toList();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
_blocRole.add(FilterUsersByJobEvent(selectedItems));
|
_blocRole.add(FilterUsersByJobEvent(
|
||||||
|
selectedJob: selectedItems,
|
||||||
|
sortOrder: _blocRole.currentSortOrder,
|
||||||
|
));
|
||||||
},
|
},
|
||||||
onSortAtoZ: () {
|
onSortAtoZ: (v) {
|
||||||
context
|
_blocRole.currentSortOrder = v;
|
||||||
.read<UserTableBloc>()
|
|
||||||
.add(const SortUsersByNameAsc());
|
|
||||||
},
|
},
|
||||||
onSortZtoA: () {
|
onSortZtoA: (v) {
|
||||||
context
|
_blocRole.currentSortOrder = v;
|
||||||
.read<UserTableBloc>()
|
|
||||||
.add(const SortUsersByNameDesc());
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (columnIndex == 3) {
|
if (columnIndex == 3) {
|
||||||
final Map<String, bool> checkboxStates = {
|
final Map<String, bool> checkboxStates = {
|
||||||
for (var item in _blocRole.roleTypes)
|
for (var item in _blocRole.roleTypes)
|
||||||
@ -263,32 +263,31 @@ class UsersPage extends StatelessWidget {
|
|||||||
list: _blocRole.roleTypes,
|
list: _blocRole.roleTypes,
|
||||||
context: context,
|
context: context,
|
||||||
checkboxStates: checkboxStates,
|
checkboxStates: checkboxStates,
|
||||||
|
isSelected: _blocRole.currentSortOrder,
|
||||||
onOkPressed: () {
|
onOkPressed: () {
|
||||||
|
_blocRole.add(FilterClearEvent());
|
||||||
final selectedItems = checkboxStates.entries
|
final selectedItems = checkboxStates.entries
|
||||||
.where((entry) => entry.value)
|
.where((entry) => entry.value)
|
||||||
.map((entry) => entry.key)
|
.map((entry) => entry.key)
|
||||||
.toList();
|
.toList();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
context
|
context.read<UserTableBloc>().add(
|
||||||
.read<UserTableBloc>()
|
FilterUsersByRoleEvent(
|
||||||
.add(FilterUsersByRoleEvent(selectedItems));
|
selectedRoles: selectedItems,
|
||||||
|
sortOrder: _blocRole.currentSortOrder));
|
||||||
},
|
},
|
||||||
onSortAtoZ: () {
|
onSortAtoZ: (v) {
|
||||||
context
|
_blocRole.currentSortOrder = v;
|
||||||
.read<UserTableBloc>()
|
|
||||||
.add(const SortUsersByNameAsc());
|
|
||||||
},
|
},
|
||||||
onSortZtoA: () {
|
onSortZtoA: (v) {
|
||||||
context
|
_blocRole.currentSortOrder = v;
|
||||||
.read<UserTableBloc>()
|
|
||||||
.add(const SortUsersByNameDesc());
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (columnIndex == 4) {
|
if (columnIndex == 4) {
|
||||||
showDateFilterMenu(
|
showDateFilterMenu(
|
||||||
context: context,
|
context: context,
|
||||||
isSelected: _blocRole.currentSortOrderDate,
|
isSelected: _blocRole.currentSortOrder,
|
||||||
aToZTap: () {
|
aToZTap: () {
|
||||||
context
|
context
|
||||||
.read<UserTableBloc>()
|
.read<UserTableBloc>()
|
||||||
@ -319,32 +318,30 @@ class UsersPage extends StatelessWidget {
|
|||||||
list: _blocRole.createdBy,
|
list: _blocRole.createdBy,
|
||||||
context: context,
|
context: context,
|
||||||
checkboxStates: checkboxStates,
|
checkboxStates: checkboxStates,
|
||||||
|
isSelected: _blocRole.currentSortOrder,
|
||||||
onOkPressed: () {
|
onOkPressed: () {
|
||||||
|
_blocRole.add(FilterClearEvent());
|
||||||
final selectedItems = checkboxStates.entries
|
final selectedItems = checkboxStates.entries
|
||||||
.where((entry) => entry.value)
|
.where((entry) => entry.value)
|
||||||
.map((entry) => entry.key)
|
.map((entry) => entry.key)
|
||||||
.toList();
|
.toList();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
_blocRole
|
_blocRole.add(FilterUsersByCreatedEvent(
|
||||||
.add(FilterUsersByCreatedEvent(selectedItems));
|
selectedCreatedBy: selectedItems,
|
||||||
|
sortOrder: _blocRole.currentSortOrder));
|
||||||
},
|
},
|
||||||
onSortAtoZ: () {
|
onSortAtoZ: (v) {
|
||||||
context
|
_blocRole.currentSortOrder = v;
|
||||||
.read<UserTableBloc>()
|
|
||||||
.add(const SortUsersByNameAsc());
|
|
||||||
},
|
},
|
||||||
onSortZtoA: () {
|
onSortZtoA: (v) {
|
||||||
context
|
_blocRole.currentSortOrder = v;
|
||||||
.read<UserTableBloc>()
|
|
||||||
.add(const SortUsersByNameDesc());
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (columnIndex == 7) {
|
if (columnIndex == 7) {
|
||||||
final Map<String, bool> checkboxStates = {
|
final Map<String, bool> checkboxStates = {
|
||||||
for (var item in _blocRole.status)
|
for (var item in _blocRole.status)
|
||||||
item: _blocRole.selectedCreatedBy.contains(item),
|
item: _blocRole.selectedStatuses.contains(item),
|
||||||
};
|
};
|
||||||
final RenderBox overlay = Overlay.of(context)
|
final RenderBox overlay = Overlay.of(context)
|
||||||
.context
|
.context
|
||||||
@ -359,24 +356,24 @@ class UsersPage extends StatelessWidget {
|
|||||||
list: _blocRole.status,
|
list: _blocRole.status,
|
||||||
context: context,
|
context: context,
|
||||||
checkboxStates: checkboxStates,
|
checkboxStates: checkboxStates,
|
||||||
|
isSelected: _blocRole.currentSortOrder,
|
||||||
onOkPressed: () {
|
onOkPressed: () {
|
||||||
|
_blocRole.add(FilterClearEvent());
|
||||||
|
|
||||||
final selectedItems = checkboxStates.entries
|
final selectedItems = checkboxStates.entries
|
||||||
.where((entry) => entry.value)
|
.where((entry) => entry.value)
|
||||||
.map((entry) => entry.key)
|
.map((entry) => entry.key)
|
||||||
.toList();
|
.toList();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
_blocRole
|
_blocRole.add(FilterUsersByDeActevateEvent(
|
||||||
.add(FilterUsersByCreatedEvent(selectedItems));
|
selectedActivate: selectedItems,
|
||||||
|
sortOrder: _blocRole.currentSortOrder));
|
||||||
},
|
},
|
||||||
onSortAtoZ: () {
|
onSortAtoZ: (v) {
|
||||||
context
|
_blocRole.currentSortOrder = v;
|
||||||
.read<UserTableBloc>()
|
|
||||||
.add(const SortUsersByNameAsc());
|
|
||||||
},
|
},
|
||||||
onSortZtoA: () {
|
onSortZtoA: (v) {
|
||||||
context
|
_blocRole.currentSortOrder = v;
|
||||||
.read<UserTableBloc>()
|
|
||||||
.add(const SortUsersByNameDesc());
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -413,7 +410,7 @@ class UsersPage extends StatelessWidget {
|
|||||||
return [
|
return [
|
||||||
Text('${user.firstName} ${user.lastName}'),
|
Text('${user.firstName} ${user.lastName}'),
|
||||||
Text(user.email),
|
Text(user.email),
|
||||||
Text(user.jobTitle ?? ''),
|
Text(user.jobTitle ?? '-'),
|
||||||
Text(user.roleType ?? ''),
|
Text(user.roleType ?? ''),
|
||||||
Text(user.createdDate ?? ''),
|
Text(user.createdDate ?? ''),
|
||||||
Text(user.createdTime ?? ''),
|
Text(user.createdTime ?? ''),
|
||||||
@ -476,11 +473,17 @@ class UsersPage extends StatelessWidget {
|
|||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return DeleteUserDialog(
|
return DeleteUserDialog(
|
||||||
onTapDelete: () {
|
onTapDelete: () async {
|
||||||
|
try {
|
||||||
_blocRole.add(DeleteUserEvent(
|
_blocRole.add(DeleteUserEvent(
|
||||||
user.uuid, context));
|
user.uuid, context));
|
||||||
},
|
await Future.delayed(
|
||||||
);
|
const Duration(seconds: 2));
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
).then((v) {
|
).then((v) {
|
||||||
if (v != null) {
|
if (v != null) {
|
||||||
@ -504,6 +507,7 @@ class UsersPage extends StatelessWidget {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
width: 500,
|
width: 500,
|
||||||
child: NumberPagination(
|
child: NumberPagination(
|
||||||
|
visiblePagesCount: 4,
|
||||||
buttonRadius: 10,
|
buttonRadius: 10,
|
||||||
selectedButtonColor: ColorsManager.secondaryColor,
|
selectedButtonColor: ColorsManager.secondaryColor,
|
||||||
buttonUnSelectedBorderColor:
|
buttonUnSelectedBorderColor:
|
||||||
|
|||||||
@ -0,0 +1,38 @@
|
|||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart';
|
||||||
|
|
||||||
|
class AddDeviceTypeBloc
|
||||||
|
extends Bloc<AddDeviceTypeEvent, List<SelectedProduct>> {
|
||||||
|
AddDeviceTypeBloc(List<SelectedProduct> initialProducts)
|
||||||
|
: super(initialProducts) {
|
||||||
|
on<UpdateProductCountEvent>(_onUpdateProductCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onUpdateProductCount(
|
||||||
|
UpdateProductCountEvent event, Emitter<List<SelectedProduct>> emit) {
|
||||||
|
final existingProduct = state.firstWhere(
|
||||||
|
(p) => p.productId == event.productId,
|
||||||
|
orElse: () => SelectedProduct(productId: event.productId, count: 0,productName: event.productName,product: event.product ),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (event.count > 0) {
|
||||||
|
if (!state.contains(existingProduct)) {
|
||||||
|
emit([
|
||||||
|
...state,
|
||||||
|
SelectedProduct(productId: event.productId, count: event.count, productName: event.productName, product: event.product)
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
final updatedList = state.map((p) {
|
||||||
|
if (p.productId == event.productId) {
|
||||||
|
return SelectedProduct(productId: p.productId, count: event.count, productName: p.productName,product: p.product);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}).toList();
|
||||||
|
emit(updatedList);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emit(state.where((p) => p.productId != event.productId).toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||||
|
|
||||||
|
abstract class AddDeviceTypeEvent extends Equatable {
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateProductCountEvent extends AddDeviceTypeEvent {
|
||||||
|
final String productId;
|
||||||
|
final int count;
|
||||||
|
final String productName;
|
||||||
|
final ProductModel product;
|
||||||
|
|
||||||
|
UpdateProductCountEvent({required this.productId, required this.count, required this.productName, required this.product});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [productId, count];
|
||||||
|
}
|
||||||
@ -0,0 +1,149 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.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/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;
|
||||||
|
final List<SelectedProduct>? initialSelectedProducts;
|
||||||
|
final List<SubspaceModel>? subspaces;
|
||||||
|
final List<Tag>? spaceTags;
|
||||||
|
final List<String>? allTags;
|
||||||
|
final String spaceName;
|
||||||
|
final Function(List<Tag>,List<SubspaceModel>?)? onSave;
|
||||||
|
|
||||||
|
|
||||||
|
const AddDeviceTypeWidget(
|
||||||
|
{super.key,
|
||||||
|
this.products,
|
||||||
|
this.initialSelectedProducts,
|
||||||
|
this.onProductsSelected,
|
||||||
|
this.subspaces,
|
||||||
|
this.allTags,
|
||||||
|
this.spaceTags,
|
||||||
|
this.onSave,
|
||||||
|
required this.spaceName});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final size = MediaQuery.of(context).size;
|
||||||
|
final crossAxisCount = size.width > 1200
|
||||||
|
? 8
|
||||||
|
: size.width > 800
|
||||||
|
? 5
|
||||||
|
: 3;
|
||||||
|
|
||||||
|
return BlocProvider(
|
||||||
|
create: (_) => AddDeviceTypeBloc(initialSelectedProducts ?? []),
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Add Devices'),
|
||||||
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: Container(
|
||||||
|
width: size.width * 0.9,
|
||||||
|
height: size.height * 0.65,
|
||||||
|
color: ColorsManager.textFieldGreyColor,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
child: ScrollableGridViewWidget(
|
||||||
|
products: products, crossAxisCount: crossAxisCount),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
CancelButton(
|
||||||
|
label: 'Cancel',
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ActionButton(
|
||||||
|
label: 'Continue',
|
||||||
|
backgroundColor: ColorsManager.secondaryColor,
|
||||||
|
foregroundColor: ColorsManager.whiteColors,
|
||||||
|
onPressed: () async {
|
||||||
|
final currentState =
|
||||||
|
context.read<AddDeviceTypeBloc>().state;
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Tag> generateInitialTags({
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialTags;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
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_type_model_event.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/counter_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_icon_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_name_widget.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
|
||||||
|
class DeviceTypeTileWidget extends StatelessWidget {
|
||||||
|
final ProductModel product;
|
||||||
|
final List<SelectedProduct> productCounts;
|
||||||
|
|
||||||
|
const DeviceTypeTileWidget({
|
||||||
|
super.key,
|
||||||
|
required this.product,
|
||||||
|
required this.productCounts,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final selectedProduct = productCounts.firstWhere(
|
||||||
|
(p) => p.productId == product.uuid,
|
||||||
|
orElse: () => SelectedProduct(
|
||||||
|
productId: product.uuid,
|
||||||
|
count: 0,
|
||||||
|
productName: product.catName,
|
||||||
|
product: product),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
elevation: 2,
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(4.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
DeviceIconWidget(icon: product.icon ?? Assets.doorSensor),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
DeviceNameWidget(name: product.name),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
CounterWidget(
|
||||||
|
isCreate: false,
|
||||||
|
initialCount: selectedProduct.count,
|
||||||
|
onCountChanged: (newCount) {
|
||||||
|
context.read<AddDeviceTypeBloc>().add(
|
||||||
|
UpdateProductCountEvent(
|
||||||
|
productId: product.uuid,
|
||||||
|
count: newCount,
|
||||||
|
productName: product.catName,
|
||||||
|
product: product),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,70 @@
|
|||||||
|
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/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';
|
||||||
|
|
||||||
|
class ScrollableGridViewWidget extends StatelessWidget {
|
||||||
|
final List<ProductModel>? products;
|
||||||
|
final int crossAxisCount;
|
||||||
|
final List<SelectedProduct>? initialProductCounts;
|
||||||
|
|
||||||
|
const ScrollableGridViewWidget({
|
||||||
|
super.key,
|
||||||
|
required this.products,
|
||||||
|
required this.crossAxisCount,
|
||||||
|
this.initialProductCounts,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final scrollController = ScrollController();
|
||||||
|
|
||||||
|
return Scrollbar(
|
||||||
|
controller: scrollController,
|
||||||
|
thumbVisibility: true,
|
||||||
|
child: BlocBuilder<AddDeviceTypeBloc, List<SelectedProduct>>(
|
||||||
|
builder: (context, productCounts) {
|
||||||
|
return GridView.builder(
|
||||||
|
controller: scrollController,
|
||||||
|
shrinkWrap: true,
|
||||||
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: crossAxisCount,
|
||||||
|
mainAxisSpacing: 6,
|
||||||
|
crossAxisSpacing: 4,
|
||||||
|
childAspectRatio: .8,
|
||||||
|
),
|
||||||
|
itemCount: products?.length ?? 0,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final product = products![index];
|
||||||
|
final initialProductCount = _findInitialProductCount(product);
|
||||||
|
|
||||||
|
return DeviceTypeTileWidget(
|
||||||
|
product: product,
|
||||||
|
productCounts: initialProductCount != null
|
||||||
|
? [...productCounts, initialProductCount]
|
||||||
|
: productCounts,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectedProduct? _findInitialProductCount(ProductModel product) {
|
||||||
|
if (initialProductCounts == null) return null;
|
||||||
|
final matchingProduct = initialProductCounts!.firstWhere(
|
||||||
|
(selectedProduct) => selectedProduct.productId == product.uuid,
|
||||||
|
orElse: () => SelectedProduct(
|
||||||
|
productId: '',
|
||||||
|
count: 0,
|
||||||
|
productName: '',
|
||||||
|
product: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return matchingProduct.productId.isNotEmpty ? matchingProduct : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +1,13 @@
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart';
|
||||||
import 'package:syncrow_web/services/product_api.dart';
|
import 'package:syncrow_web/services/product_api.dart';
|
||||||
import 'package:syncrow_web/services/space_mana_api.dart';
|
import 'package:syncrow_web/services/space_mana_api.dart';
|
||||||
import 'package:syncrow_web/services/space_model_mang_api.dart';
|
import 'package:syncrow_web/services/space_model_mang_api.dart';
|
||||||
@ -52,10 +55,14 @@ class SpaceManagementBloc
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var prevSpaceModels = await fetchSpaceModels(previousState);
|
||||||
|
|
||||||
emit(SpaceManagementLoaded(
|
emit(SpaceManagementLoaded(
|
||||||
communities: updatedCommunities,
|
communities: updatedCommunities,
|
||||||
products: previousState.products,
|
products: previousState.products,
|
||||||
selectedCommunity: previousState.selectedCommunity,
|
selectedCommunity: previousState.selectedCommunity,
|
||||||
|
spaceModels: prevSpaceModels,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -66,6 +73,41 @@ class SpaceManagementBloc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<SpaceTemplateModel>> fetchSpaceModels(
|
||||||
|
SpaceManagementState previousState) async {
|
||||||
|
try {
|
||||||
|
List<SpaceTemplateModel> allSpaces = [];
|
||||||
|
List<SpaceTemplateModel> prevSpaceModels = [];
|
||||||
|
|
||||||
|
if (previousState is SpaceManagementLoaded ||
|
||||||
|
previousState is BlankState) {
|
||||||
|
prevSpaceModels = List<SpaceTemplateModel>.from(
|
||||||
|
(previousState as dynamic).spaceModels ?? [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevSpaceModels.isEmpty) {
|
||||||
|
bool hasNext = true;
|
||||||
|
int page = 1;
|
||||||
|
|
||||||
|
while (hasNext) {
|
||||||
|
final spaces = await _spaceModelApi.listSpaceModels(page: page);
|
||||||
|
if (spaces.isNotEmpty) {
|
||||||
|
allSpaces.addAll(spaces);
|
||||||
|
page++;
|
||||||
|
} else {
|
||||||
|
hasNext = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prevSpaceModels = await _spaceModelApi.listSpaceModels(page: 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return allSpaces;
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _onloadProducts() async {
|
void _onloadProducts() async {
|
||||||
if (_cachedProducts == null) {
|
if (_cachedProducts == null) {
|
||||||
final products = await _productApi.fetchProducts();
|
final products = await _productApi.fetchProducts();
|
||||||
@ -89,19 +131,24 @@ class SpaceManagementBloc
|
|||||||
return await _api.getSpaceHierarchy(communityUuid);
|
return await _api.getSpaceHierarchy(communityUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onNewCommunity(
|
Future<void> _onNewCommunity(
|
||||||
NewCommunityEvent event,
|
NewCommunityEvent event,
|
||||||
Emitter<SpaceManagementState> emit,
|
Emitter<SpaceManagementState> emit,
|
||||||
) {
|
) async {
|
||||||
try {
|
try {
|
||||||
|
final previousState = state;
|
||||||
|
|
||||||
if (event.communities.isEmpty) {
|
if (event.communities.isEmpty) {
|
||||||
emit(const SpaceManagementError('No communities provided.'));
|
emit(const SpaceManagementError('No communities provided.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var prevSpaceModels = await fetchSpaceModels(previousState);
|
||||||
|
|
||||||
emit(BlankState(
|
emit(BlankState(
|
||||||
communities: event.communities,
|
communities: event.communities,
|
||||||
products: _cachedProducts ?? [],
|
products: _cachedProducts ?? [],
|
||||||
|
spaceModels: prevSpaceModels,
|
||||||
));
|
));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
emit(SpaceManagementError('Error loading communities: $error'));
|
emit(SpaceManagementError('Error loading communities: $error'));
|
||||||
@ -112,6 +159,7 @@ class SpaceManagementBloc
|
|||||||
BlankStateEvent event, Emitter<SpaceManagementState> emit) async {
|
BlankStateEvent event, Emitter<SpaceManagementState> emit) async {
|
||||||
try {
|
try {
|
||||||
final previousState = state;
|
final previousState = state;
|
||||||
|
var prevSpaceModels = await fetchSpaceModels(previousState);
|
||||||
|
|
||||||
if (previousState is SpaceManagementLoaded ||
|
if (previousState is SpaceManagementLoaded ||
|
||||||
previousState is BlankState) {
|
previousState is BlankState) {
|
||||||
@ -119,6 +167,7 @@ class SpaceManagementBloc
|
|||||||
emit(BlankState(
|
emit(BlankState(
|
||||||
communities: List<CommunityModel>.from(prevCommunities),
|
communities: List<CommunityModel>.from(prevCommunities),
|
||||||
products: _cachedProducts ?? [],
|
products: _cachedProducts ?? [],
|
||||||
|
spaceModels: prevSpaceModels,
|
||||||
));
|
));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -139,6 +188,7 @@ class SpaceManagementBloc
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
emit(BlankState(
|
emit(BlankState(
|
||||||
|
spaceModels: prevSpaceModels,
|
||||||
communities: updatedCommunities,
|
communities: updatedCommunities,
|
||||||
products: _cachedProducts ?? [],
|
products: _cachedProducts ?? [],
|
||||||
));
|
));
|
||||||
@ -151,6 +201,7 @@ class SpaceManagementBloc
|
|||||||
LoadCommunityAndSpacesEvent event,
|
LoadCommunityAndSpacesEvent event,
|
||||||
Emitter<SpaceManagementState> emit,
|
Emitter<SpaceManagementState> emit,
|
||||||
) async {
|
) async {
|
||||||
|
var prevState = state;
|
||||||
emit(SpaceManagementLoading());
|
emit(SpaceManagementLoading());
|
||||||
try {
|
try {
|
||||||
_onloadProducts();
|
_onloadProducts();
|
||||||
@ -172,8 +223,11 @@ class SpaceManagementBloc
|
|||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final prevSpaceModels = await fetchSpaceModels(prevState);
|
||||||
emit(SpaceManagementLoaded(
|
emit(SpaceManagementLoaded(
|
||||||
communities: updatedCommunities, products: _cachedProducts ?? []));
|
communities: updatedCommunities,
|
||||||
|
products: _cachedProducts ?? [],
|
||||||
|
spaceModels: prevSpaceModels));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(SpaceManagementError('Error loading communities and spaces: $e'));
|
emit(SpaceManagementError('Error loading communities and spaces: $e'));
|
||||||
}
|
}
|
||||||
@ -213,6 +267,7 @@ class SpaceManagementBloc
|
|||||||
try {
|
try {
|
||||||
CommunityModel? newCommunity =
|
CommunityModel? newCommunity =
|
||||||
await _api.createCommunity(event.name, event.description);
|
await _api.createCommunity(event.name, event.description);
|
||||||
|
var prevSpaceModels = await fetchSpaceModels(previousState);
|
||||||
|
|
||||||
if (newCommunity != null) {
|
if (newCommunity != null) {
|
||||||
if (previousState is SpaceManagementLoaded ||
|
if (previousState is SpaceManagementLoaded ||
|
||||||
@ -222,6 +277,7 @@ class SpaceManagementBloc
|
|||||||
);
|
);
|
||||||
final updatedCommunities = prevCommunities..add(newCommunity);
|
final updatedCommunities = prevCommunities..add(newCommunity);
|
||||||
emit(SpaceManagementLoaded(
|
emit(SpaceManagementLoaded(
|
||||||
|
spaceModels: prevSpaceModels,
|
||||||
communities: updatedCommunities,
|
communities: updatedCommunities,
|
||||||
products: _cachedProducts ?? [],
|
products: _cachedProducts ?? [],
|
||||||
selectedCommunity: newCommunity,
|
selectedCommunity: newCommunity,
|
||||||
@ -276,12 +332,16 @@ class SpaceManagementBloc
|
|||||||
final communities = List<CommunityModel>.from(
|
final communities = List<CommunityModel>.from(
|
||||||
(previousState as dynamic).communities,
|
(previousState as dynamic).communities,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final spaceModels = List<SpaceTemplateModel>.from(
|
||||||
|
(previousState as dynamic).spaceModels,
|
||||||
|
);
|
||||||
emit(SpaceManagementLoaded(
|
emit(SpaceManagementLoaded(
|
||||||
communities: communities,
|
communities: communities,
|
||||||
products: _cachedProducts ?? [],
|
products: _cachedProducts ?? [],
|
||||||
selectedCommunity: selectedCommunity,
|
selectedCommunity: selectedCommunity,
|
||||||
selectedSpace: selectedSpace,
|
selectedSpace: selectedSpace,
|
||||||
));
|
spaceModels: spaceModels ?? []));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(SpaceManagementError('Error updating state: $e'));
|
emit(SpaceManagementError('Error updating state: $e'));
|
||||||
@ -304,7 +364,7 @@ class SpaceManagementBloc
|
|||||||
emit(SpaceCreationSuccess(spaces: updatedSpaces));
|
emit(SpaceCreationSuccess(spaces: updatedSpaces));
|
||||||
|
|
||||||
if (previousState is SpaceManagementLoaded) {
|
if (previousState is SpaceManagementLoaded) {
|
||||||
_updateLoadedState(
|
await _updateLoadedState(
|
||||||
previousState,
|
previousState,
|
||||||
allSpaces,
|
allSpaces,
|
||||||
event.communityUuid,
|
event.communityUuid,
|
||||||
@ -322,12 +382,14 @@ class SpaceManagementBloc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateLoadedState(
|
Future<void> _updateLoadedState(
|
||||||
SpaceManagementLoaded previousState,
|
SpaceManagementLoaded previousState,
|
||||||
List<SpaceModel> allSpaces,
|
List<SpaceModel> allSpaces,
|
||||||
String communityUuid,
|
String communityUuid,
|
||||||
Emitter<SpaceManagementState> emit,
|
Emitter<SpaceManagementState> emit,
|
||||||
) {
|
) async {
|
||||||
|
var prevSpaceModels = await fetchSpaceModels(previousState);
|
||||||
|
|
||||||
final communities = List<CommunityModel>.from(previousState.communities);
|
final communities = List<CommunityModel>.from(previousState.communities);
|
||||||
|
|
||||||
for (var community in communities) {
|
for (var community in communities) {
|
||||||
@ -338,7 +400,7 @@ class SpaceManagementBloc
|
|||||||
products: _cachedProducts ?? [],
|
products: _cachedProducts ?? [],
|
||||||
selectedCommunity: community,
|
selectedCommunity: community,
|
||||||
selectedSpace: null,
|
selectedSpace: null,
|
||||||
));
|
spaceModels: prevSpaceModels));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -358,9 +420,10 @@ class SpaceManagementBloc
|
|||||||
await _api.deleteSpace(communityUuid, parent.uuid!);
|
await _api.deleteSpace(communityUuid, parent.uuid!);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow; // Decide whether to stop execution or continue
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
orderedSpaces.removeWhere((space) => parentsToDelete.contains(space));
|
||||||
|
|
||||||
for (var space in orderedSpaces) {
|
for (var space in orderedSpaces) {
|
||||||
try {
|
try {
|
||||||
@ -377,6 +440,21 @@ class SpaceManagementBloc
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Call create if the space does not have a UUID
|
// Call create if the space does not have a UUID
|
||||||
|
final List<CreateTagBodyModel> tagBodyModels = space.tags != null
|
||||||
|
? space.tags!.map((tag) => tag.toCreateTagBodyModel()).toList()
|
||||||
|
: [];
|
||||||
|
|
||||||
|
final createSubspaceBodyModels = space.subspaces?.map((subspace) {
|
||||||
|
final tagBodyModels = subspace.tags
|
||||||
|
?.map((tag) => tag.toCreateTagBodyModel())
|
||||||
|
.toList() ??
|
||||||
|
[];
|
||||||
|
return CreateSubspaceModel()
|
||||||
|
..subspaceName = subspace.subspaceName
|
||||||
|
..tags = tagBodyModels;
|
||||||
|
}).toList() ??
|
||||||
|
[];
|
||||||
|
|
||||||
final response = await _api.createSpace(
|
final response = await _api.createSpace(
|
||||||
communityId: communityUuid,
|
communityId: communityUuid,
|
||||||
name: space.name,
|
name: space.name,
|
||||||
@ -385,6 +463,9 @@ class SpaceManagementBloc
|
|||||||
position: space.position,
|
position: space.position,
|
||||||
icon: space.icon,
|
icon: space.icon,
|
||||||
direction: space.incomingConnection?.direction,
|
direction: space.incomingConnection?.direction,
|
||||||
|
spaceModelUuid: space.spaceModel?.uuid,
|
||||||
|
tags: tagBodyModels,
|
||||||
|
subspaces: createSubspaceBodyModels,
|
||||||
);
|
);
|
||||||
space.uuid = response?.uuid;
|
space.uuid = response?.uuid;
|
||||||
}
|
}
|
||||||
@ -424,6 +505,8 @@ class SpaceManagementBloc
|
|||||||
SpaceModelLoadEvent event, Emitter<SpaceManagementState> emit) async {
|
SpaceModelLoadEvent event, Emitter<SpaceManagementState> emit) async {
|
||||||
emit(SpaceManagementLoading());
|
emit(SpaceManagementLoading());
|
||||||
try {
|
try {
|
||||||
|
var prevState = state;
|
||||||
|
|
||||||
List<CommunityModel> communities = await _api.fetchCommunities();
|
List<CommunityModel> communities = await _api.fetchCommunities();
|
||||||
|
|
||||||
List<CommunityModel> updatedCommunities = await Future.wait(
|
List<CommunityModel> updatedCommunities = await Future.wait(
|
||||||
@ -442,12 +525,12 @@ class SpaceManagementBloc
|
|||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
List<SpaceTemplateModel> spaceModels =
|
var prevSpaceModels = await fetchSpaceModels(prevState);
|
||||||
await _spaceModelApi.listSpaceModels(page: 1);
|
|
||||||
emit(SpaceModelLoaded(
|
emit(SpaceModelLoaded(
|
||||||
communities: updatedCommunities,
|
communities: updatedCommunities,
|
||||||
products: _cachedProducts ?? [],
|
products: _cachedProducts ?? [],
|
||||||
spaceModels: spaceModels));
|
spaceModels: prevSpaceModels));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(SpaceManagementError('Error loading communities and spaces: $e'));
|
emit(SpaceManagementError('Error loading communities and spaces: $e'));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,12 +20,14 @@ class SpaceManagementLoaded extends SpaceManagementState {
|
|||||||
final List<ProductModel> products;
|
final List<ProductModel> products;
|
||||||
CommunityModel? selectedCommunity;
|
CommunityModel? selectedCommunity;
|
||||||
SpaceModel? selectedSpace;
|
SpaceModel? selectedSpace;
|
||||||
|
List<SpaceTemplateModel>? spaceModels;
|
||||||
|
|
||||||
SpaceManagementLoaded(
|
SpaceManagementLoaded(
|
||||||
{required this.communities,
|
{required this.communities,
|
||||||
required this.products,
|
required this.products,
|
||||||
this.selectedCommunity,
|
this.selectedCommunity,
|
||||||
this.selectedSpace});
|
this.selectedSpace,
|
||||||
|
this.spaceModels});
|
||||||
}
|
}
|
||||||
|
|
||||||
class SpaceModelManagenetLoaded extends SpaceManagementState {
|
class SpaceModelManagenetLoaded extends SpaceManagementState {
|
||||||
@ -35,11 +37,10 @@ class SpaceModelManagenetLoaded extends SpaceManagementState {
|
|||||||
class BlankState extends SpaceManagementState {
|
class BlankState extends SpaceManagementState {
|
||||||
final List<CommunityModel> communities;
|
final List<CommunityModel> communities;
|
||||||
final List<ProductModel> products;
|
final List<ProductModel> products;
|
||||||
|
List<SpaceTemplateModel>? spaceModels;
|
||||||
|
|
||||||
BlankState({
|
BlankState(
|
||||||
required this.communities,
|
{required this.communities, required this.products, this.spaceModels});
|
||||||
required this.products,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class SpaceCreationSuccess extends SpaceManagementState {
|
class SpaceCreationSuccess extends SpaceManagementState {
|
||||||
@ -61,7 +62,7 @@ class SpaceManagementError extends SpaceManagementState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SpaceModelLoaded extends SpaceManagementState {
|
class SpaceModelLoaded extends SpaceManagementState {
|
||||||
final List<SpaceTemplateModel> spaceModels;
|
List<SpaceTemplateModel> spaceModels;
|
||||||
final List<ProductModel> products;
|
final List<ProductModel> products;
|
||||||
final List<CommunityModel> communities;
|
final List<CommunityModel> communities;
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart';
|
||||||
|
|
||||||
|
class CreateSubspaceModel {
|
||||||
|
late String subspaceName;
|
||||||
|
late List<CreateTagBodyModel>? tags;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'subspaceName': subspaceName,
|
||||||
|
'tags': tags?.map((tag) => tag.toJson()).toList(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -66,4 +66,25 @@ class ProductModel {
|
|||||||
String toString() {
|
String toString() {
|
||||||
return 'ProductModel(uuid: $uuid, catName: $catName, prodId: $prodId, prodType: $prodType, name: $name, icon: $icon)';
|
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,11 +1,13 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_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/space_template_model.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
enum SpaceStatus { newSpace, modified, unchanged, deleted }
|
enum SpaceStatus { newSpace, modified, unchanged, deleted, parentDeleted }
|
||||||
|
|
||||||
class SpaceModel {
|
class SpaceModel {
|
||||||
String? uuid;
|
String? uuid;
|
||||||
@ -21,6 +23,9 @@ class SpaceModel {
|
|||||||
bool isHovered;
|
bool isHovered;
|
||||||
SpaceStatus status;
|
SpaceStatus status;
|
||||||
String internalId;
|
String internalId;
|
||||||
|
SpaceTemplateModel? spaceModel;
|
||||||
|
List<Tag>? tags;
|
||||||
|
List<SubspaceModel>? subspaces;
|
||||||
|
|
||||||
List<Connection> outgoingConnections = []; // Connections from this space
|
List<Connection> outgoingConnections = []; // Connections from this space
|
||||||
Connection? incomingConnection; // Connections to this space
|
Connection? incomingConnection; // Connections to this space
|
||||||
@ -40,6 +45,9 @@ class SpaceModel {
|
|||||||
this.isHovered = false,
|
this.isHovered = false,
|
||||||
this.incomingConnection,
|
this.incomingConnection,
|
||||||
this.status = SpaceStatus.unchanged,
|
this.status = SpaceStatus.unchanged,
|
||||||
|
this.spaceModel,
|
||||||
|
this.tags,
|
||||||
|
this.subspaces,
|
||||||
}) : internalId = internalId ?? const Uuid().v4();
|
}) : internalId = internalId ?? const Uuid().v4();
|
||||||
|
|
||||||
factory SpaceModel.fromJson(Map<String, dynamic> json,
|
factory SpaceModel.fromJson(Map<String, dynamic> json,
|
||||||
@ -62,6 +70,11 @@ class SpaceModel {
|
|||||||
name: json['spaceName'],
|
name: json['spaceName'],
|
||||||
isPrivate: json['isPrivate'] ?? false,
|
isPrivate: json['isPrivate'] ?? false,
|
||||||
invitationCode: json['invitationCode'],
|
invitationCode: json['invitationCode'],
|
||||||
|
subspaces: (json['subspaces'] as List<dynamic>?)
|
||||||
|
?.where((e) => e is Map<String, dynamic>) // Validate type
|
||||||
|
.map((e) => SubspaceModel.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
[],
|
||||||
parent: parentInternalId != null
|
parent: parentInternalId != null
|
||||||
? SpaceModel(
|
? SpaceModel(
|
||||||
internalId: parentInternalId,
|
internalId: parentInternalId,
|
||||||
@ -83,6 +96,11 @@ class SpaceModel {
|
|||||||
icon: json['icon'] ?? Assets.location,
|
icon: json['icon'] ?? Assets.location,
|
||||||
position: Offset(json['x'] ?? 0, json['y'] ?? 0),
|
position: Offset(json['x'] ?? 0, json['y'] ?? 0),
|
||||||
isHovered: false,
|
isHovered: false,
|
||||||
|
tags: (json['tags'] as List<dynamic>?)
|
||||||
|
?.where((item) => item is Map<String, dynamic>) // Validate type
|
||||||
|
.map((item) => Tag.fromJson(item as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (json['incomingConnections'] != null &&
|
if (json['incomingConnections'] != null &&
|
||||||
@ -108,6 +126,7 @@ class SpaceModel {
|
|||||||
'isPrivate': isPrivate,
|
'isPrivate': isPrivate,
|
||||||
'invitationCode': invitationCode,
|
'invitationCode': invitationCode,
|
||||||
'parent': parent?.uuid,
|
'parent': parent?.uuid,
|
||||||
|
'subspaces': subspaces?.map((e) => e.toJson()).toList(),
|
||||||
'community': community?.toMap(),
|
'community': community?.toMap(),
|
||||||
'children': children.map((child) => child.toMap()).toList(),
|
'children': children.map((child) => child.toMap()).toList(),
|
||||||
'icon': icon,
|
'icon': icon,
|
||||||
@ -115,6 +134,7 @@ class SpaceModel {
|
|||||||
'isHovered': isHovered,
|
'isHovered': isHovered,
|
||||||
'outgoingConnections': outgoingConnections.map((c) => c.toMap()).toList(),
|
'outgoingConnections': outgoingConnections.map((c) => c.toMap()).toList(),
|
||||||
'incomingConnection': incomingConnection?.toMap(),
|
'incomingConnection': incomingConnection?.toMap(),
|
||||||
|
'tags': tags?.map((e) => e.toJson()).toList(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,3 +142,28 @@ class SpaceModel {
|
|||||||
outgoingConnections.add(connection);
|
outgoingConnections.add(connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension SpaceExtensions on SpaceModel {
|
||||||
|
List<String> listAllTagValues() {
|
||||||
|
final List<String> tagValues = [];
|
||||||
|
|
||||||
|
if (tags != null) {
|
||||||
|
tagValues.addAll(
|
||||||
|
tags!.map((tag) => tag.tag ?? '').where((tag) => tag.isNotEmpty));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subspaces != null) {
|
||||||
|
for (final subspace in subspaces!) {
|
||||||
|
if (subspace.tags != null) {
|
||||||
|
tagValues.addAll(
|
||||||
|
subspace.tags!
|
||||||
|
.map((tag) => tag.tag ?? '')
|
||||||
|
.where((tag) => tag.isNotEmpty),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tagValues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
110
lib/pages/spaces_management/all_spaces/model/subspace_model.dart
Normal file
110
lib/pages/spaces_management/all_spaces/model/subspace_model.dart
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||||
|
import 'package:syncrow_web/utils/constants/action_enum.dart';
|
||||||
|
|
||||||
|
import 'tag.dart';
|
||||||
|
|
||||||
|
class SubspaceModel {
|
||||||
|
final String? uuid;
|
||||||
|
String subspaceName;
|
||||||
|
final bool disabled;
|
||||||
|
List<Tag>? tags;
|
||||||
|
|
||||||
|
SubspaceModel({
|
||||||
|
this.uuid,
|
||||||
|
required this.subspaceName,
|
||||||
|
required this.disabled,
|
||||||
|
this.tags,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SubspaceModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return SubspaceModel(
|
||||||
|
uuid: json['uuid'] ?? '',
|
||||||
|
subspaceName: json['subspaceName'] ?? '',
|
||||||
|
disabled: json['disabled'] ?? false,
|
||||||
|
tags: (json['tags'] as List<dynamic>?)
|
||||||
|
?.map((item) => Tag.fromJson(item))
|
||||||
|
.toList() ??
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'uuid': uuid,
|
||||||
|
'subspaceName': subspaceName,
|
||||||
|
'disabled': disabled,
|
||||||
|
'tags': tags?.map((e) => e.toJson()).toList() ?? [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateSubspaceModel {
|
||||||
|
final String uuid;
|
||||||
|
final Action action;
|
||||||
|
final String? subspaceName;
|
||||||
|
final List<UpdateTag>? tags;
|
||||||
|
UpdateSubspaceModel({
|
||||||
|
required this.action,
|
||||||
|
required this.uuid,
|
||||||
|
this.subspaceName,
|
||||||
|
this.tags,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory UpdateSubspaceModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return UpdateSubspaceModel(
|
||||||
|
action: ActionExtension.fromValue(json['action']),
|
||||||
|
uuid: json['uuid'] ?? '',
|
||||||
|
subspaceName: json['subspaceName'] ?? '',
|
||||||
|
tags: (json['tags'] as List)
|
||||||
|
.map((item) => UpdateTag.fromJson(item))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'action': action.value,
|
||||||
|
'uuid': uuid,
|
||||||
|
'subspaceName': subspaceName,
|
||||||
|
'tags': tags?.map((e) => e.toJson()).toList() ?? [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateTag {
|
||||||
|
final Action action;
|
||||||
|
final String? uuid;
|
||||||
|
final String tag;
|
||||||
|
final bool disabled;
|
||||||
|
final ProductModel? product;
|
||||||
|
|
||||||
|
UpdateTag({
|
||||||
|
required this.action,
|
||||||
|
this.uuid,
|
||||||
|
required this.tag,
|
||||||
|
required this.disabled,
|
||||||
|
this.product,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory UpdateTag.fromJson(Map<String, dynamic> json) {
|
||||||
|
return UpdateTag(
|
||||||
|
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(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
68
lib/pages/spaces_management/all_spaces/model/tag.dart
Normal file
68
lib/pages/spaces_management/all_spaces/model/tag.dart
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
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();
|
||||||
|
|
||||||
|
factory Tag.fromJson(Map<String, dynamic> json) {
|
||||||
|
final String internalId = json['internalId'] ?? const Uuid().v4();
|
||||||
|
|
||||||
|
return Tag(
|
||||||
|
uuid: json['uuid'] ?? '',
|
||||||
|
internalId: internalId,
|
||||||
|
tag: json['tag'] ?? '',
|
||||||
|
product: json['product'] != null
|
||||||
|
? ProductModel.fromMap(json['product'])
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Tag copyWith({
|
||||||
|
String? tag,
|
||||||
|
ProductModel? product,
|
||||||
|
String? location,
|
||||||
|
}) {
|
||||||
|
return Tag(
|
||||||
|
tag: tag ?? this.tag,
|
||||||
|
product: product ?? this.product,
|
||||||
|
location: location ?? this.location,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'uuid': uuid,
|
||||||
|
'tag': tag,
|
||||||
|
'product': product?.toMap(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TagModelExtensions on Tag {
|
||||||
|
TagBodyModel toTagBodyModel() {
|
||||||
|
return TagBodyModel()
|
||||||
|
..uuid = uuid ?? ''
|
||||||
|
..tag = tag ?? ''
|
||||||
|
..productUuid = product?.uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateTagBodyModel toCreateTagBodyModel() {
|
||||||
|
return CreateTagBodyModel()
|
||||||
|
..tag = tag ?? ''
|
||||||
|
..productUuid = product?.uuid;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -51,6 +51,7 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
|
|||||||
selectedCommunity: null,
|
selectedCommunity: null,
|
||||||
selectedSpace: null,
|
selectedSpace: null,
|
||||||
products: state.products,
|
products: state.products,
|
||||||
|
shouldNavigateToSpaceModelPage: false,
|
||||||
);
|
);
|
||||||
} else if (state is SpaceManagementLoaded) {
|
} else if (state is SpaceManagementLoaded) {
|
||||||
return LoadedSpaceView(
|
return LoadedSpaceView(
|
||||||
@ -58,12 +59,16 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
|
|||||||
selectedCommunity: state.selectedCommunity,
|
selectedCommunity: state.selectedCommunity,
|
||||||
selectedSpace: state.selectedSpace,
|
selectedSpace: state.selectedSpace,
|
||||||
products: state.products,
|
products: state.products,
|
||||||
|
spaceModels: state.spaceModels,
|
||||||
|
shouldNavigateToSpaceModelPage: false,
|
||||||
);
|
);
|
||||||
} else if (state is SpaceModelLoaded) {
|
} else if (state is SpaceModelLoaded) {
|
||||||
return LoadedSpaceView(
|
return LoadedSpaceView(
|
||||||
communities: state.communities,
|
communities: state.communities,
|
||||||
products: state.products,
|
products: state.products,
|
||||||
spaceModels: state.spaceModels);
|
spaceModels: state.spaceModels,
|
||||||
|
shouldNavigateToSpaceModelPage: true,
|
||||||
|
);
|
||||||
} else if (state is SpaceManagementError) {
|
} else if (state is SpaceManagementError) {
|
||||||
return Center(child: Text('Error: ${state.errorMessage}'));
|
return Center(child: Text('Error: ${state.errorMessage}'));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -137,6 +137,7 @@ class _AddDeviceWidgetState extends State<AddDeviceWidget> {
|
|||||||
_buildDeviceName(product, size),
|
_buildDeviceName(product, size),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
CounterWidget(
|
CounterWidget(
|
||||||
|
isCreate: false,
|
||||||
initialCount: selectedProduct.count,
|
initialCount: selectedProduct.count,
|
||||||
onCountChanged: (newCount) {
|
onCountChanged: (newCount) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.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/create_community/view/create_community_dialog.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/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
@ -17,6 +18,7 @@ class CommunityStructureHeader extends StatefulWidget {
|
|||||||
final ValueChanged<String> onNameSubmitted;
|
final ValueChanged<String> onNameSubmitted;
|
||||||
final List<CommunityModel> communities;
|
final List<CommunityModel> communities;
|
||||||
final CommunityModel? community;
|
final CommunityModel? community;
|
||||||
|
final SpaceModel? selectedSpace;
|
||||||
|
|
||||||
const CommunityStructureHeader(
|
const CommunityStructureHeader(
|
||||||
{super.key,
|
{super.key,
|
||||||
@ -29,7 +31,8 @@ class CommunityStructureHeader extends StatefulWidget {
|
|||||||
required this.onEditName,
|
required this.onEditName,
|
||||||
required this.onNameSubmitted,
|
required this.onNameSubmitted,
|
||||||
this.community,
|
this.community,
|
||||||
required this.communities});
|
required this.communities,
|
||||||
|
this.selectedSpace});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CommunityStructureHeader> createState() =>
|
State<CommunityStructureHeader> createState() =>
|
||||||
@ -137,11 +140,9 @@ class _CommunityStructureHeaderState extends State<CommunityStructureHeader> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (widget.isSave) ...[
|
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_buildActionButtons(theme),
|
_buildActionButtons(theme),
|
||||||
],
|
],
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -152,12 +153,20 @@ class _CommunityStructureHeaderState extends State<CommunityStructureHeader> {
|
|||||||
alignment: WrapAlignment.end,
|
alignment: WrapAlignment.end,
|
||||||
spacing: 10,
|
spacing: 10,
|
||||||
children: [
|
children: [
|
||||||
|
if (widget.isSave)
|
||||||
_buildButton(
|
_buildButton(
|
||||||
label: "Save",
|
label: "Save",
|
||||||
icon: const Icon(Icons.save,
|
icon: const Icon(Icons.save,
|
||||||
size: 18, color: ColorsManager.spaceColor),
|
size: 18, color: ColorsManager.spaceColor),
|
||||||
onPressed: widget.onSave,
|
onPressed: widget.onSave,
|
||||||
theme: theme),
|
theme: theme),
|
||||||
|
if(widget.selectedSpace!= null)
|
||||||
|
_buildButton(
|
||||||
|
label: "Delete",
|
||||||
|
icon: const Icon(Icons.delete,
|
||||||
|
size: 18, color: ColorsManager.warningRed),
|
||||||
|
onPressed: widget.onDelete,
|
||||||
|
theme: theme),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,12 +11,15 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_pr
|
|||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/blank_community_widget.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/blank_community_widget.dart';
|
||||||
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/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/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/curved_line_painter.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_card_widget.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_container_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';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class CommunityStructureArea extends StatefulWidget {
|
class CommunityStructureArea extends StatefulWidget {
|
||||||
@ -26,6 +29,7 @@ class CommunityStructureArea extends StatefulWidget {
|
|||||||
final ValueChanged<SpaceModel?>? onSpaceSelected;
|
final ValueChanged<SpaceModel?>? onSpaceSelected;
|
||||||
final List<CommunityModel> communities;
|
final List<CommunityModel> communities;
|
||||||
final List<SpaceModel> spaces;
|
final List<SpaceModel> spaces;
|
||||||
|
final List<SpaceTemplateModel>? spaceModels;
|
||||||
|
|
||||||
CommunityStructureArea({
|
CommunityStructureArea({
|
||||||
this.selectedCommunity,
|
this.selectedCommunity,
|
||||||
@ -34,6 +38,7 @@ class CommunityStructureArea extends StatefulWidget {
|
|||||||
this.products,
|
this.products,
|
||||||
required this.spaces,
|
required this.spaces,
|
||||||
this.onSpaceSelected,
|
this.onSpaceSelected,
|
||||||
|
this.spaceModels,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -126,6 +131,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
isEditingName: isEditingName,
|
isEditingName: isEditingName,
|
||||||
nameController: _nameController,
|
nameController: _nameController,
|
||||||
onSave: _saveSpaces,
|
onSave: _saveSpaces,
|
||||||
|
selectedSpace: widget.selectedSpace,
|
||||||
onDelete: _onDelete,
|
onDelete: _onDelete,
|
||||||
onEditName: () {
|
onEditName: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -171,7 +177,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
painter: CurvedLinePainter([connection])),
|
painter: CurvedLinePainter([connection])),
|
||||||
),
|
),
|
||||||
for (var entry in spaces.asMap().entries)
|
for (var entry in spaces.asMap().entries)
|
||||||
if (entry.value.status != SpaceStatus.deleted)
|
if (entry.value.status != SpaceStatus.deleted &&
|
||||||
|
entry.value.status != SpaceStatus.parentDeleted)
|
||||||
Positioned(
|
Positioned(
|
||||||
left: entry.value.position.dx,
|
left: entry.value.position.dx,
|
||||||
top: entry.value.position.dy,
|
top: entry.value.position.dy,
|
||||||
@ -284,12 +291,16 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return CreateSpaceDialog(
|
return CreateSpaceDialog(
|
||||||
products: widget.products,
|
products: widget.products,
|
||||||
|
spaceModels: widget.spaceModels,
|
||||||
parentSpace: parentIndex != null ? spaces[parentIndex] : null,
|
parentSpace: parentIndex != null ? spaces[parentIndex] : null,
|
||||||
onCreateSpace: (String name, String icon,
|
onCreateSpace: (String name,
|
||||||
List<SelectedProduct> selectedProducts) {
|
String icon,
|
||||||
|
List<SelectedProduct> selectedProducts,
|
||||||
|
SpaceTemplateModel? spaceModel,
|
||||||
|
List<SubspaceModel>? subspaces,
|
||||||
|
List<Tag>? tags) {
|
||||||
setState(() {
|
setState(() {
|
||||||
// Set the first space in the center or use passed position
|
// Set the first space in the center or use passed position
|
||||||
|
|
||||||
Offset centerPosition =
|
Offset centerPosition =
|
||||||
position ?? _getCenterPosition(screenSize);
|
position ?? _getCenterPosition(screenSize);
|
||||||
SpaceModel newSpace = SpaceModel(
|
SpaceModel newSpace = SpaceModel(
|
||||||
@ -299,7 +310,9 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
isPrivate: false,
|
isPrivate: false,
|
||||||
children: [],
|
children: [],
|
||||||
status: SpaceStatus.newSpace,
|
status: SpaceStatus.newSpace,
|
||||||
);
|
spaceModel: spaceModel,
|
||||||
|
subspaces: subspaces,
|
||||||
|
tags: tags);
|
||||||
|
|
||||||
if (parentIndex != null && direction != null) {
|
if (parentIndex != null && direction != null) {
|
||||||
SpaceModel parentSpace = spaces[parentIndex];
|
SpaceModel parentSpace = spaces[parentIndex];
|
||||||
@ -335,12 +348,19 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
icon: space.icon,
|
icon: space.icon,
|
||||||
editSpace: space,
|
editSpace: space,
|
||||||
isEdit: true,
|
isEdit: true,
|
||||||
onCreateSpace: (String name, String icon,
|
onCreateSpace: (String name,
|
||||||
List<SelectedProduct> selectedProducts) {
|
String icon,
|
||||||
|
List<SelectedProduct> selectedProducts,
|
||||||
|
SpaceTemplateModel? spaceModel,
|
||||||
|
List<SubspaceModel>? subspaces,
|
||||||
|
List<Tag>? tags) {
|
||||||
setState(() {
|
setState(() {
|
||||||
// Update the space's properties
|
// Update the space's properties
|
||||||
space.name = name;
|
space.name = name;
|
||||||
space.icon = icon;
|
space.icon = icon;
|
||||||
|
space.spaceModel = spaceModel;
|
||||||
|
space.subspaces = subspaces;
|
||||||
|
space.tags = tags;
|
||||||
|
|
||||||
if (space.status != SpaceStatus.newSpace) {
|
if (space.status != SpaceStatus.newSpace) {
|
||||||
space.status = SpaceStatus.modified; // Mark as modified
|
space.status = SpaceStatus.modified; // Mark as modified
|
||||||
@ -363,10 +383,11 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
List<SpaceModel> result = [];
|
List<SpaceModel> result = [];
|
||||||
|
|
||||||
void flatten(SpaceModel space) {
|
void flatten(SpaceModel space) {
|
||||||
if (space.status == SpaceStatus.deleted) return;
|
if (space.status == SpaceStatus.deleted ||
|
||||||
|
space.status == SpaceStatus.parentDeleted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
result.add(space);
|
result.add(space);
|
||||||
|
|
||||||
for (var child in space.children) {
|
for (var child in space.children) {
|
||||||
flatten(child);
|
flatten(child);
|
||||||
}
|
}
|
||||||
@ -434,21 +455,15 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onDelete() {
|
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) {
|
if (widget.selectedSpace != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
for (var space in spaces) {
|
for (var space in spaces) {
|
||||||
if (space.uuid == widget.selectedSpace?.uuid) {
|
if (space.internalId == widget.selectedSpace?.internalId) {
|
||||||
space.status = SpaceStatus.deleted;
|
space.status = SpaceStatus.deleted;
|
||||||
_markChildrenAsDeleted(space);
|
_markChildrenAsDeleted(space);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_removeConnectionsForDeletedSpaces();
|
_removeConnectionsForDeletedSpaces();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -456,7 +471,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
|
|
||||||
void _markChildrenAsDeleted(SpaceModel parent) {
|
void _markChildrenAsDeleted(SpaceModel parent) {
|
||||||
for (var child in parent.children) {
|
for (var child in parent.children) {
|
||||||
child.status = SpaceStatus.deleted;
|
child.status = SpaceStatus.parentDeleted;
|
||||||
|
|
||||||
_markChildrenAsDeleted(child);
|
_markChildrenAsDeleted(child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -464,7 +480,9 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
void _removeConnectionsForDeletedSpaces() {
|
void _removeConnectionsForDeletedSpaces() {
|
||||||
connections.removeWhere((connection) {
|
connections.removeWhere((connection) {
|
||||||
return connection.startSpace.status == SpaceStatus.deleted ||
|
return connection.startSpace.status == SpaceStatus.deleted ||
|
||||||
connection.endSpace.status == SpaceStatus.deleted;
|
connection.endSpace.status == SpaceStatus.deleted ||
|
||||||
|
connection.startSpace.status == SpaceStatus.parentDeleted ||
|
||||||
|
connection.endSpace.status == SpaceStatus.parentDeleted;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,12 +4,14 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
|||||||
class CounterWidget extends StatefulWidget {
|
class CounterWidget extends StatefulWidget {
|
||||||
final int initialCount;
|
final int initialCount;
|
||||||
final ValueChanged<int> onCountChanged;
|
final ValueChanged<int> onCountChanged;
|
||||||
|
final bool isCreate;
|
||||||
|
|
||||||
const CounterWidget({
|
const CounterWidget(
|
||||||
Key? key,
|
{Key? key,
|
||||||
this.initialCount = 0,
|
this.initialCount = 0,
|
||||||
required this.onCountChanged,
|
required this.onCountChanged,
|
||||||
}) : super(key: key);
|
required this.isCreate})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CounterWidget> createState() => _CounterWidgetState();
|
State<CounterWidget> createState() => _CounterWidgetState();
|
||||||
@ -53,25 +55,26 @@ class _CounterWidgetState extends State<CounterWidget> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
_buildCounterButton(Icons.remove, _decrementCounter),
|
_buildCounterButton(Icons.remove, _decrementCounter,!widget.isCreate ),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'$_counter',
|
'$_counter',
|
||||||
style: theme.textTheme.bodyLarge?.copyWith(color: ColorsManager.spaceColor),
|
style: theme.textTheme.bodyLarge
|
||||||
|
?.copyWith(color: ColorsManager.spaceColor),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
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(
|
return GestureDetector(
|
||||||
onTap: onPressed,
|
onTap: isDisabled? null: onPressed,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
icon,
|
icon,
|
||||||
color: ColorsManager.spaceColor,
|
color: isDisabled? ColorsManager.spaceColor.withOpacity(0.3): ColorsManager.spaceColor,
|
||||||
size: 18,
|
size: 18,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,19 +2,23 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
|
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
|
||||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_widget.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_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/widgets/dialogs/icon_selection_dialog.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/hoverable_button.dart';
|
import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
import 'package:syncrow_web/utils/constants/space_icon_const.dart';
|
import 'package:syncrow_web/utils/constants/space_icon_const.dart';
|
||||||
|
|
||||||
class CreateSpaceDialog extends StatefulWidget {
|
class CreateSpaceDialog extends StatefulWidget {
|
||||||
final Function(String, String, List<SelectedProduct> selectedProducts)
|
final Function(String, String, List<SelectedProduct> selectedProducts,
|
||||||
onCreateSpace;
|
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) onCreateSpace;
|
||||||
final List<ProductModel>? products;
|
final List<ProductModel>? products;
|
||||||
final String? name;
|
final String? name;
|
||||||
final String? icon;
|
final String? icon;
|
||||||
@ -22,6 +26,9 @@ class CreateSpaceDialog extends StatefulWidget {
|
|||||||
final List<SelectedProduct> selectedProducts;
|
final List<SelectedProduct> selectedProducts;
|
||||||
final SpaceModel? parentSpace;
|
final SpaceModel? parentSpace;
|
||||||
final SpaceModel? editSpace;
|
final SpaceModel? editSpace;
|
||||||
|
final List<SpaceTemplateModel>? spaceModels;
|
||||||
|
final List<SubspaceModel>? subspaces;
|
||||||
|
final List<Tag>? tags;
|
||||||
|
|
||||||
const CreateSpaceDialog(
|
const CreateSpaceDialog(
|
||||||
{super.key,
|
{super.key,
|
||||||
@ -32,7 +39,10 @@ class CreateSpaceDialog extends StatefulWidget {
|
|||||||
this.icon,
|
this.icon,
|
||||||
this.isEdit = false,
|
this.isEdit = false,
|
||||||
this.editSpace,
|
this.editSpace,
|
||||||
this.selectedProducts = const []});
|
this.selectedProducts = const [],
|
||||||
|
this.spaceModels,
|
||||||
|
this.subspaces,
|
||||||
|
this.tags});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
CreateSpaceDialogState createState() => CreateSpaceDialogState();
|
CreateSpaceDialogState createState() => CreateSpaceDialogState();
|
||||||
@ -40,12 +50,15 @@ class CreateSpaceDialog extends StatefulWidget {
|
|||||||
|
|
||||||
class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
||||||
String selectedIcon = Assets.location;
|
String selectedIcon = Assets.location;
|
||||||
|
SpaceTemplateModel? selectedSpaceModel;
|
||||||
String enteredName = '';
|
String enteredName = '';
|
||||||
List<SelectedProduct> selectedProducts = [];
|
List<SelectedProduct> selectedProducts = [];
|
||||||
late TextEditingController nameController;
|
late TextEditingController nameController;
|
||||||
bool isOkButtonEnabled = false;
|
bool isOkButtonEnabled = false;
|
||||||
bool isNameFieldInvalid = false;
|
bool isNameFieldInvalid = false;
|
||||||
bool isNameFieldExist = false;
|
bool isNameFieldExist = false;
|
||||||
|
List<SubspaceModel>? subspaces;
|
||||||
|
List<Tag>? tags;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -58,33 +71,33 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
enteredName.isNotEmpty || nameController.text.isNotEmpty;
|
enteredName.isNotEmpty || nameController.text.isNotEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: widget.isEdit
|
title: widget.isEdit
|
||||||
? const Text('Edit Space')
|
? const Text('Edit Space')
|
||||||
: const Text('Create New Space'),
|
: const Text('Create New Space'),
|
||||||
backgroundColor: ColorsManager.whiteColors,
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
width: screenWidth * 0.5, // Limit dialog width
|
width: screenWidth * 0.5,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
// Scrollable content to prevent overflow
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
// crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
|
const SizedBox(height: 50),
|
||||||
Stack(
|
Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
width: screenWidth * 0.1, // Adjusted width
|
width: screenWidth * 0.1,
|
||||||
height: screenWidth * 0.1, // Adjusted height
|
height: screenWidth * 0.1,
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: ColorsManager.boxColor,
|
color: ColorsManager.boxColor,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
@ -96,30 +109,33 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
height: screenWidth * 0.04,
|
height: screenWidth * 0.04,
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 6,
|
top: 20,
|
||||||
right: 6,
|
right: 20,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: _showIconSelectionDialog,
|
onTap: _showIconSelectionDialog,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: screenWidth * 0.020,
|
width: 24,
|
||||||
height: screenWidth * 0.020,
|
height: 24,
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: ColorsManager.whiteColors,
|
color: Colors.white,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: SvgPicture.asset(
|
child: SvgPicture.asset(
|
||||||
Assets.iconEdit,
|
Assets.iconEdit,
|
||||||
width: screenWidth * 0.06,
|
width: 16,
|
||||||
height: screenWidth * 0.06,
|
height: 16,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
Expanded(
|
Expanded(
|
||||||
// Ensure the text field expands responsively
|
flex: 2,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -143,7 +159,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
style: const TextStyle(color: ColorsManager.blackColor),
|
style: const TextStyle(color: Colors.black),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Please enter the name',
|
hintText: 'Please enter the name',
|
||||||
hintStyle: const TextStyle(
|
hintStyle: const TextStyle(
|
||||||
@ -193,26 +209,130 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
?.copyWith(color: ColorsManager.red),
|
?.copyWith(color: ColorsManager.red),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 10),
|
||||||
if (selectedProducts.isNotEmpty)
|
selectedSpaceModel == null
|
||||||
_buildSelectedProductsButtons(widget.products ?? [])
|
? DefaultButton(
|
||||||
else
|
|
||||||
DefaultButton(
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDialog(
|
_showLinkSpaceModelDialog(context);
|
||||||
context: context,
|
|
||||||
builder: (context) => AddDeviceWidget(
|
|
||||||
products: widget.products,
|
|
||||||
onProductsSelected: (selectedProductsMap) {
|
|
||||||
setState(() {
|
|
||||||
selectedProducts = selectedProductsMap;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
backgroundColor: ColorsManager.textFieldGreyColor,
|
backgroundColor: ColorsManager.textFieldGreyColor,
|
||||||
foregroundColor: ColorsManager.blackColor,
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Container(
|
||||||
|
width: screenWidth * 0.35,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 10.0, horizontal: 16.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.boxColor,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 8.0,
|
||||||
|
runSpacing: 8.0,
|
||||||
|
children: [
|
||||||
|
Chip(
|
||||||
|
label: Text(
|
||||||
|
selectedSpaceModel?.modelName ?? '',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorsManager.spaceColor),
|
||||||
|
),
|
||||||
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
side: const BorderSide(
|
||||||
|
color: ColorsManager.transparentColor,
|
||||||
|
width: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
deleteIcon: Container(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorsManager.lightGrayColor,
|
||||||
|
width: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 16,
|
||||||
|
color: ColorsManager.lightGrayColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onDeleted: () => setState(() {
|
||||||
|
this.selectedSpaceModel = null;
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 25),
|
||||||
|
const Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Divider(
|
||||||
|
color: ColorsManager.neutralGray,
|
||||||
|
thickness: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
|
child: Text(
|
||||||
|
'OR',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Divider(
|
||||||
|
color: ColorsManager.neutralGray,
|
||||||
|
thickness: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 25),
|
||||||
|
subspaces == null
|
||||||
|
? DefaultButton(
|
||||||
|
onPressed: () {
|
||||||
|
_showSubSpaceDialog(context, enteredName, [],
|
||||||
|
false, widget.products, subspaces);
|
||||||
|
},
|
||||||
|
backgroundColor: ColorsManager.textFieldGreyColor,
|
||||||
|
foregroundColor: Colors.black,
|
||||||
borderColor: ColorsManager.neutralGray,
|
borderColor: ColorsManager.neutralGray,
|
||||||
borderRadius: 16.0,
|
borderRadius: 16.0,
|
||||||
padding: 10.0, // Reduced padding for smaller size
|
padding: 10.0, // Reduced padding for smaller size
|
||||||
@ -233,7 +353,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
const SizedBox(width: 3),
|
const SizedBox(width: 3),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Add devices / Assign a space model',
|
'Create sub space',
|
||||||
overflow: TextOverflow
|
overflow: TextOverflow
|
||||||
.ellipsis, // Prevent overflow
|
.ellipsis, // Prevent overflow
|
||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
@ -243,12 +363,194 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)),
|
),
|
||||||
],
|
)
|
||||||
|
: SizedBox(
|
||||||
|
width: screenWidth * 0.35,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.textFieldGreyColor,
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorsManager.textFieldGreyColor,
|
||||||
|
width: 3.0, // Border width
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 8.0,
|
||||||
|
runSpacing: 8.0,
|
||||||
|
children: [
|
||||||
|
if (subspaces != null)
|
||||||
|
...subspaces!.map(
|
||||||
|
(subspace) => Chip(
|
||||||
|
label: Text(
|
||||||
|
subspace.subspaceName,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorsManager
|
||||||
|
.spaceColor), // Text color
|
||||||
|
),
|
||||||
|
backgroundColor: ColorsManager
|
||||||
|
.whiteColors, // Chip background color
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
16), // Rounded chip
|
||||||
|
side: const BorderSide(
|
||||||
|
color: ColorsManager
|
||||||
|
.spaceColor), // Border color
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
_showSubSpaceDialog(
|
||||||
|
context,
|
||||||
|
enteredName,
|
||||||
|
[],
|
||||||
|
false,
|
||||||
|
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),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
(tags?.isNotEmpty == true ||
|
||||||
|
subspaces?.any((subspace) =>
|
||||||
|
subspace.tags?.isNotEmpty == true) ==
|
||||||
|
true)
|
||||||
|
? SizedBox(
|
||||||
|
width: screenWidth * 0.25,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.textFieldGreyColor,
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorsManager.textFieldGreyColor,
|
||||||
|
width: 3.0, // Border width
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 8.0,
|
||||||
|
runSpacing: 8.0,
|
||||||
|
children: [
|
||||||
|
// Combine tags from spaceModel and subspaces
|
||||||
|
..._groupTags([
|
||||||
|
...?tags,
|
||||||
|
...?subspaces?.expand(
|
||||||
|
(subspace) => subspace.tags ?? [])
|
||||||
|
]).entries.map(
|
||||||
|
(entry) => Chip(
|
||||||
|
avatar: SizedBox(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
entry.key.icon ??
|
||||||
|
'assets/icons/gateway.svg',
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
label: Text(
|
||||||
|
'x${entry.value}', // Show count
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorsManager.spaceColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
backgroundColor:
|
||||||
|
ColorsManager.whiteColors,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(16),
|
||||||
|
side: const BorderSide(
|
||||||
|
color: ColorsManager.spaceColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: DefaultButton(
|
||||||
|
onPressed: () {
|
||||||
|
_showTagCreateDialog(
|
||||||
|
context, enteredName, widget.products);
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -277,8 +579,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
? enteredName
|
? enteredName
|
||||||
: (widget.name ?? '');
|
: (widget.name ?? '');
|
||||||
if (newName.isNotEmpty) {
|
if (newName.isNotEmpty) {
|
||||||
widget.onCreateSpace(
|
widget.onCreateSpace(newName, selectedIcon,
|
||||||
newName, selectedIcon, selectedProducts);
|
selectedProducts, selectedSpaceModel,subspaces,tags);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -313,74 +615,6 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSelectedProductsButtons(List<ProductModel> products) {
|
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
width: screenWidth * 0.6,
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: ColorsManager.textFieldGreyColor,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(
|
|
||||||
color: ColorsManager.neutralGray,
|
|
||||||
width: 2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Wrap(
|
|
||||||
spacing: 8,
|
|
||||||
runSpacing: 8,
|
|
||||||
children: [
|
|
||||||
for (var i = 0; i < selectedProducts.length; i++) ...[
|
|
||||||
HoverableButton(
|
|
||||||
iconPath:
|
|
||||||
_mapIconToProduct(selectedProducts[i].productId, products),
|
|
||||||
text: 'x${selectedProducts[i].count}',
|
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
selectedProducts.remove(selectedProducts[i]);
|
|
||||||
});
|
|
||||||
// Handle button tap
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (i < selectedProducts.length - 1)
|
|
||||||
const SizedBox(
|
|
||||||
width: 2), // Add space except after the last button
|
|
||||||
],
|
|
||||||
const SizedBox(width: 2),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AddDeviceWidget(
|
|
||||||
products: widget.products,
|
|
||||||
initialSelectedProducts: selectedProducts,
|
|
||||||
onProductsSelected: (selectedProductsMap) {
|
|
||||||
setState(() {
|
|
||||||
selectedProducts = selectedProductsMap;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: ColorsManager.whiteColors,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.add,
|
|
||||||
color: ColorsManager.spaceColor,
|
|
||||||
size: 24,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isNameConflict(String value) {
|
bool _isNameConflict(String value) {
|
||||||
return (widget.parentSpace?.children.any((child) => child.name == value) ??
|
return (widget.parentSpace?.children.any((child) => child.name == value) ??
|
||||||
false) ||
|
false) ||
|
||||||
@ -390,20 +624,133 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
|
|||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _mapIconToProduct(String uuid, List<ProductModel> products) {
|
void _showLinkSpaceModelDialog(BuildContext context) {
|
||||||
// Find the product with the matching UUID
|
showDialog(
|
||||||
final product = products.firstWhere(
|
context: context,
|
||||||
(product) => product.uuid == uuid,
|
builder: (BuildContext context) {
|
||||||
orElse: () => ProductModel(
|
return LinkSpaceModelDialog(
|
||||||
uuid: '',
|
spaceModels: widget.spaceModels ?? [],
|
||||||
catName: '',
|
onSave: (selectedModel) {
|
||||||
prodId: '',
|
if (selectedModel != null) {
|
||||||
prodType: '',
|
setState(() {
|
||||||
name: '',
|
selectedSpaceModel = selectedModel;
|
||||||
icon: Assets.presenceSensor,
|
subspaces = null;
|
||||||
),
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return product.icon ?? Assets.presenceSensor;
|
void _showSubSpaceDialog(
|
||||||
|
BuildContext context,
|
||||||
|
String name,
|
||||||
|
final List<Tag>? spaceTags,
|
||||||
|
bool isEdit,
|
||||||
|
List<ProductModel>? products,
|
||||||
|
final List<SubspaceModel>? existingSubSpaces) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return CreateSubSpaceDialog(
|
||||||
|
spaceName: name,
|
||||||
|
dialogTitle: 'Create Sub-space',
|
||||||
|
spaceTags: spaceTags,
|
||||||
|
isEdit: isEdit,
|
||||||
|
products: products,
|
||||||
|
existingSubSpaces: existingSubSpaces,
|
||||||
|
onSave: (slectedSubspaces) {
|
||||||
|
if (slectedSubspaces != null) {
|
||||||
|
setState(() {
|
||||||
|
subspaces = slectedSubspaces;
|
||||||
|
selectedSpaceModel = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,12 +11,13 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/space_tem
|
|||||||
import 'package:syncrow_web/pages/spaces_management/space_model/view/space_model_page.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/view/space_model_page.dart';
|
||||||
import 'package:syncrow_web/services/space_model_mang_api.dart';
|
import 'package:syncrow_web/services/space_model_mang_api.dart';
|
||||||
|
|
||||||
class LoadedSpaceView extends StatefulWidget {
|
class LoadedSpaceView extends StatelessWidget {
|
||||||
final List<CommunityModel> communities;
|
final List<CommunityModel> communities;
|
||||||
final CommunityModel? selectedCommunity;
|
final CommunityModel? selectedCommunity;
|
||||||
final SpaceModel? selectedSpace;
|
final SpaceModel? selectedSpace;
|
||||||
final List<ProductModel>? products;
|
final List<ProductModel>? products;
|
||||||
final List<SpaceTemplateModel>? spaceModels;
|
final List<SpaceTemplateModel>? spaceModels;
|
||||||
|
final bool shouldNavigateToSpaceModelPage;
|
||||||
|
|
||||||
const LoadedSpaceView({
|
const LoadedSpaceView({
|
||||||
super.key,
|
super.key,
|
||||||
@ -25,17 +26,11 @@ class LoadedSpaceView extends StatefulWidget {
|
|||||||
this.selectedSpace,
|
this.selectedSpace,
|
||||||
this.products,
|
this.products,
|
||||||
this.spaceModels,
|
this.spaceModels,
|
||||||
|
required this.shouldNavigateToSpaceModelPage
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
|
||||||
_LoadedStateViewState createState() => _LoadedStateViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LoadedStateViewState extends State<LoadedSpaceView> {
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final bool hasSpaceModels =
|
|
||||||
widget.spaceModels != null && widget.spaceModels!.isNotEmpty;
|
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
@ -43,29 +38,29 @@ class _LoadedStateViewState extends State<LoadedSpaceView> {
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
SidebarWidget(
|
SidebarWidget(
|
||||||
communities: widget.communities,
|
communities: communities,
|
||||||
selectedSpaceUuid: widget.selectedSpace?.uuid ??
|
selectedSpaceUuid:
|
||||||
widget.selectedCommunity?.uuid ??
|
selectedSpace?.uuid ?? selectedCommunity?.uuid ?? '',
|
||||||
'',
|
|
||||||
),
|
),
|
||||||
hasSpaceModels
|
shouldNavigateToSpaceModelPage
|
||||||
? Expanded(
|
? Expanded(
|
||||||
child: BlocProvider(
|
child: BlocProvider(
|
||||||
create: (context) => SpaceModelBloc(
|
create: (context) => SpaceModelBloc(
|
||||||
api: SpaceModelManagementApi(),
|
api: SpaceModelManagementApi(),
|
||||||
initialSpaceModels: widget.spaceModels ?? [],
|
initialSpaceModels: spaceModels ?? [],
|
||||||
),
|
),
|
||||||
child: SpaceModelPage(
|
child: SpaceModelPage(
|
||||||
products: widget.products,
|
products: products,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: CommunityStructureArea(
|
: CommunityStructureArea(
|
||||||
selectedCommunity: widget.selectedCommunity,
|
selectedCommunity: selectedCommunity,
|
||||||
selectedSpace: widget.selectedSpace,
|
selectedSpace: selectedSpace,
|
||||||
spaces: widget.selectedCommunity?.spaces ?? [],
|
spaces: selectedCommunity?.spaces ?? [],
|
||||||
products: widget.products,
|
products: products,
|
||||||
communities: widget.communities,
|
communities: communities,
|
||||||
|
spaceModels: spaceModels,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -0,0 +1,118 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/hoverable_button.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
|
||||||
|
class SelectedProductsButtons extends StatelessWidget {
|
||||||
|
final List<ProductModel> products;
|
||||||
|
final List<SelectedProduct> selectedProducts;
|
||||||
|
final Function(List<SelectedProduct>) onProductsUpdated;
|
||||||
|
final BuildContext context;
|
||||||
|
|
||||||
|
const SelectedProductsButtons({
|
||||||
|
Key? key,
|
||||||
|
required this.products,
|
||||||
|
required this.selectedProducts,
|
||||||
|
required this.onProductsUpdated,
|
||||||
|
required this.context,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: screenWidth * 0.6,
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.textFieldGreyColor,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorsManager.neutralGray,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: [
|
||||||
|
..._buildSelectedProductButtons(),
|
||||||
|
_buildAddButton(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildSelectedProductButtons() {
|
||||||
|
return [
|
||||||
|
for (var i = 0; i < selectedProducts.length; i++) ...[
|
||||||
|
HoverableButton(
|
||||||
|
iconPath: _mapIconToProduct(selectedProducts[i].productId, products),
|
||||||
|
text: 'x${selectedProducts[i].count}',
|
||||||
|
onTap: () {
|
||||||
|
_removeProduct(i);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (i < selectedProducts.length - 1)
|
||||||
|
const SizedBox(width: 2), // Add space except after the last button
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAddButton() {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: _showAddDeviceDialog,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.add,
|
||||||
|
color: ColorsManager.spaceColor,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showAddDeviceDialog() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AddDeviceWidget(
|
||||||
|
products: products,
|
||||||
|
initialSelectedProducts: selectedProducts,
|
||||||
|
onProductsSelected: (selectedProductsMap) {
|
||||||
|
onProductsUpdated(selectedProductsMap);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _removeProduct(int index) {
|
||||||
|
final updatedProducts = [...selectedProducts];
|
||||||
|
updatedProducts.removeAt(index);
|
||||||
|
onProductsUpdated(updatedProducts);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _mapIconToProduct(String uuid, List<ProductModel> products) {
|
||||||
|
// Find the product with the matching UUID
|
||||||
|
final product = products.firstWhere(
|
||||||
|
(product) => product.uuid == uuid,
|
||||||
|
orElse: () => ProductModel(
|
||||||
|
uuid: '',
|
||||||
|
catName: '',
|
||||||
|
prodId: '',
|
||||||
|
prodType: '',
|
||||||
|
name: '',
|
||||||
|
icon: Assets.presenceSensor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return product.icon ?? Assets.presenceSensor;
|
||||||
|
}
|
||||||
|
}
|
||||||
148
lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart
Normal file
148
lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart';
|
||||||
|
|
||||||
|
class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
|
||||||
|
AssignTagBloc() : super(AssignTagInitial()) {
|
||||||
|
on<InitializeTags>((event, emit) {
|
||||||
|
final initialTags = event.initialTags ?? [];
|
||||||
|
|
||||||
|
final existingTagCounts = <String, int>{};
|
||||||
|
for (var tag in initialTags) {
|
||||||
|
if (tag.product != null) {
|
||||||
|
existingTagCounts[tag.product!.uuid] =
|
||||||
|
(existingTagCounts[tag.product!.uuid] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final allTags = <Tag>[];
|
||||||
|
|
||||||
|
for (var selectedProduct in event.addedProducts) {
|
||||||
|
final existingCount = existingTagCounts[selectedProduct.productId] ?? 0;
|
||||||
|
|
||||||
|
if (selectedProduct.count == 0 ||
|
||||||
|
selectedProduct.count <= existingCount) {
|
||||||
|
allTags.addAll(initialTags
|
||||||
|
.where((tag) => tag.product?.uuid == selectedProduct.productId));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final missingCount = selectedProduct.count - existingCount;
|
||||||
|
|
||||||
|
allTags.addAll(initialTags
|
||||||
|
.where((tag) => tag.product?.uuid == selectedProduct.productId));
|
||||||
|
|
||||||
|
if (missingCount > 0) {
|
||||||
|
allTags.addAll(List.generate(
|
||||||
|
missingCount,
|
||||||
|
(index) => Tag(
|
||||||
|
tag: '',
|
||||||
|
product: selectedProduct.product,
|
||||||
|
location: 'Main Space',
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(AssignTagLoaded(
|
||||||
|
tags: allTags,
|
||||||
|
isSaveEnabled: _validateTags(allTags),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
on<UpdateTagEvent>((event, emit) {
|
||||||
|
final currentState = state;
|
||||||
|
|
||||||
|
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
|
||||||
|
final tags = List<Tag>.from(currentState.tags);
|
||||||
|
tags[event.index].tag = event.tag;
|
||||||
|
emit(AssignTagLoaded(
|
||||||
|
tags: tags,
|
||||||
|
isSaveEnabled: _validateTags(tags),
|
||||||
|
errorMessage: _getValidationError(tags),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
on<UpdateLocation>((event, emit) {
|
||||||
|
final currentState = state;
|
||||||
|
|
||||||
|
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
|
||||||
|
final tags = List<Tag>.from(currentState.tags);
|
||||||
|
|
||||||
|
// Use copyWith for immutability
|
||||||
|
tags[event.index] =
|
||||||
|
tags[event.index].copyWith(location: event.location);
|
||||||
|
|
||||||
|
emit(AssignTagLoaded(
|
||||||
|
tags: tags,
|
||||||
|
isSaveEnabled: _validateTags(tags),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
on<ValidateTags>((event, emit) {
|
||||||
|
final currentState = state;
|
||||||
|
|
||||||
|
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
|
||||||
|
final tags = List<Tag>.from(currentState.tags);
|
||||||
|
|
||||||
|
emit(AssignTagLoaded(
|
||||||
|
tags: tags,
|
||||||
|
isSaveEnabled: _validateTags(tags),
|
||||||
|
errorMessage: _getValidationError(tags),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
on<DeleteTag>((event, emit) {
|
||||||
|
final currentState = state;
|
||||||
|
|
||||||
|
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
|
||||||
|
final updatedTags = List<Tag>.from(currentState.tags)
|
||||||
|
..remove(event.tagToDelete);
|
||||||
|
|
||||||
|
emit(AssignTagLoaded(
|
||||||
|
tags: updatedTags,
|
||||||
|
isSaveEnabled: _validateTags(updatedTags),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
emit(const AssignTagLoaded(
|
||||||
|
tags: [],
|
||||||
|
isSaveEnabled: false,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _getValidationError(List<Tag> tags) {
|
||||||
|
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
|
||||||
|
if (hasEmptyTag) return 'Tags cannot be empty.';
|
||||||
|
final duplicateTags = tags
|
||||||
|
.map((tag) => tag.tag?.trim() ?? '')
|
||||||
|
.fold<Map<String, int>>({}, (map, tag) {
|
||||||
|
map[tag] = (map[tag] ?? 0) + 1;
|
||||||
|
return map;
|
||||||
|
})
|
||||||
|
.entries
|
||||||
|
.where((entry) => entry.value > 1)
|
||||||
|
.map((entry) => entry.key)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (duplicateTags.isNotEmpty) {
|
||||||
|
return 'Duplicate tags found: ${duplicateTags.join(', ')}';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
||||||
|
|
||||||
|
abstract class AssignTagEvent extends Equatable {
|
||||||
|
const AssignTagEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class InitializeTags extends AssignTagEvent {
|
||||||
|
final List<Tag>? initialTags;
|
||||||
|
final List<SelectedProduct> addedProducts;
|
||||||
|
|
||||||
|
const InitializeTags({
|
||||||
|
required this.initialTags,
|
||||||
|
required this.addedProducts,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [initialTags ?? [], addedProducts];
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateTagEvent extends AssignTagEvent {
|
||||||
|
final int index;
|
||||||
|
final String tag;
|
||||||
|
|
||||||
|
const UpdateTagEvent({required this.index, required this.tag});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [index, tag];
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateLocation extends AssignTagEvent {
|
||||||
|
final int index;
|
||||||
|
final String location;
|
||||||
|
|
||||||
|
const UpdateLocation({required this.index, required this.location});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [index, location];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ValidateTags extends AssignTagEvent {}
|
||||||
|
|
||||||
|
class DeleteTag extends AssignTagEvent {
|
||||||
|
final Tag tagToDelete;
|
||||||
|
final List<Tag> tags;
|
||||||
|
|
||||||
|
const DeleteTag({required this.tagToDelete, required this.tags});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [tagToDelete, tags];
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
||||||
|
|
||||||
|
abstract class AssignTagState extends Equatable {
|
||||||
|
const AssignTagState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssignTagInitial extends AssignTagState {}
|
||||||
|
|
||||||
|
class AssignTagLoading extends AssignTagState {}
|
||||||
|
|
||||||
|
class AssignTagLoaded extends AssignTagState {
|
||||||
|
final List<Tag> tags;
|
||||||
|
final bool isSaveEnabled;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
const AssignTagLoaded({
|
||||||
|
required this.tags,
|
||||||
|
required this.isSaveEnabled,
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [tags, isSaveEnabled];
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssignTagError extends AssignTagState {
|
||||||
|
final String errorMessage;
|
||||||
|
|
||||||
|
const AssignTagError(this.errorMessage);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [errorMessage];
|
||||||
|
}
|
||||||
@ -0,0 +1,340 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
|
||||||
|
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/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/assign_tag/bloc/assign_tag_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class AssignTagDialog extends StatelessWidget {
|
||||||
|
final List<ProductModel>? products;
|
||||||
|
final List<SubspaceModel>? subspaces;
|
||||||
|
final List<Tag>? initialTags;
|
||||||
|
final ValueChanged<List<Tag>>? onTagsAssigned;
|
||||||
|
final List<SelectedProduct> addedProducts;
|
||||||
|
final List<String>? allTags;
|
||||||
|
final String spaceName;
|
||||||
|
final String title;
|
||||||
|
final Function(List<Tag>, List<SubspaceModel>?)? onSave;
|
||||||
|
|
||||||
|
const AssignTagDialog(
|
||||||
|
{Key? key,
|
||||||
|
required this.products,
|
||||||
|
required this.subspaces,
|
||||||
|
required this.addedProducts,
|
||||||
|
this.initialTags,
|
||||||
|
this.onTagsAssigned,
|
||||||
|
this.allTags,
|
||||||
|
required this.spaceName,
|
||||||
|
required this.title,
|
||||||
|
this.onSave})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final List<String> locations =
|
||||||
|
(subspaces ?? []).map((subspace) => subspace.subspaceName).toList();
|
||||||
|
return BlocProvider(
|
||||||
|
create: (_) => AssignTagBloc()
|
||||||
|
..add(InitializeTags(
|
||||||
|
initialTags: initialTags,
|
||||||
|
addedProducts: addedProducts,
|
||||||
|
)),
|
||||||
|
child: BlocBuilder<AssignTagBloc, AssignTagState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state is AssignTagLoaded) {
|
||||||
|
final controllers = List.generate(
|
||||||
|
state.tags.length,
|
||||||
|
(index) => TextEditingController(text: state.tags[index].tag),
|
||||||
|
);
|
||||||
|
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(title),
|
||||||
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
child: DataTable(
|
||||||
|
headingRowColor: WidgetStateProperty.all(
|
||||||
|
ColorsManager.dataHeaderGrey),
|
||||||
|
border: TableBorder.all(
|
||||||
|
color: ColorsManager.dataHeaderGrey,
|
||||||
|
width: 1,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
columns: [
|
||||||
|
DataColumn(
|
||||||
|
label: Text('#',
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.bodyMedium)),
|
||||||
|
DataColumn(
|
||||||
|
label: Text('Device',
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.bodyMedium)),
|
||||||
|
DataColumn(
|
||||||
|
label: Text('Tag',
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.bodyMedium)),
|
||||||
|
DataColumn(
|
||||||
|
label: Text('Location',
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.bodyMedium)),
|
||||||
|
],
|
||||||
|
rows: state.tags.isEmpty
|
||||||
|
? [
|
||||||
|
const DataRow(cells: [
|
||||||
|
DataCell(
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
'No Data Available',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: ColorsManager.lightGrayColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DataCell(SizedBox()),
|
||||||
|
DataCell(SizedBox()),
|
||||||
|
DataCell(SizedBox()),
|
||||||
|
])
|
||||||
|
]
|
||||||
|
: List.generate(state.tags.length, (index) {
|
||||||
|
final tag = state.tags[index];
|
||||||
|
final controller = controllers[index];
|
||||||
|
|
||||||
|
return DataRow(
|
||||||
|
cells: [
|
||||||
|
DataCell(Text(index.toString())),
|
||||||
|
DataCell(
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
tag.product?.name ?? 'Unknown',
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
)),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close,
|
||||||
|
color: ColorsManager.warningRed,
|
||||||
|
size: 16),
|
||||||
|
onPressed: () {
|
||||||
|
context.read<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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(context)
|
||||||
|
.size
|
||||||
|
.width *
|
||||||
|
0.15,
|
||||||
|
child: PopupMenuButton<String>(
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.arrow_drop_down,
|
||||||
|
color:
|
||||||
|
ColorsManager.blackColor),
|
||||||
|
onSelected: (value) {
|
||||||
|
controller.text = value;
|
||||||
|
context
|
||||||
|
.read<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();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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) {
|
||||||
|
context
|
||||||
|
.read<AssignTagBloc>()
|
||||||
|
.add(UpdateLocation(
|
||||||
|
index: index,
|
||||||
|
location: value,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (state.errorMessage != null)
|
||||||
|
Text(
|
||||||
|
state.errorMessage!,
|
||||||
|
style: const TextStyle(color: ColorsManager.warningRed),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: CancelButton(
|
||||||
|
label: 'Cancel',
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: DefaultButton(
|
||||||
|
borderRadius: 10,
|
||||||
|
backgroundColor: state.isSaveEnabled
|
||||||
|
? ColorsManager.secondaryColor
|
||||||
|
: ColorsManager.grayColor,
|
||||||
|
foregroundColor: ColorsManager.whiteColors,
|
||||||
|
onPressed: state.isSaveEnabled
|
||||||
|
? () async {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
final assignedTags = <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);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
child: const Text('Save'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else if (state is AssignTagLoading) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
} else {
|
||||||
|
return const Center(child: Text('Something went wrong.'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -40,7 +40,7 @@ class AssignTagModelBloc
|
|||||||
(index) => TagModel(
|
(index) => TagModel(
|
||||||
tag: '',
|
tag: '',
|
||||||
product: selectedProduct.product,
|
product: selectedProduct.product,
|
||||||
location: 'None',
|
location: 'Main Space',
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -49,12 +49,11 @@ class AssignTagModelBloc
|
|||||||
emit(AssignTagModelLoaded(
|
emit(AssignTagModelLoaded(
|
||||||
tags: allTags,
|
tags: allTags,
|
||||||
isSaveEnabled: _validateTags(allTags),
|
isSaveEnabled: _validateTags(allTags),
|
||||||
));
|
errorMessage: ''));
|
||||||
});
|
});
|
||||||
|
|
||||||
on<UpdateTag>((event, emit) {
|
on<UpdateTag>((event, emit) {
|
||||||
final currentState = state;
|
final currentState = state;
|
||||||
|
|
||||||
if (currentState is AssignTagModelLoaded &&
|
if (currentState is AssignTagModelLoaded &&
|
||||||
currentState.tags.isNotEmpty) {
|
currentState.tags.isNotEmpty) {
|
||||||
final tags = List<TagModel>.from(currentState.tags);
|
final tags = List<TagModel>.from(currentState.tags);
|
||||||
@ -122,17 +121,20 @@ class AssignTagModelBloc
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _validateTags(List<TagModel> tags) {
|
bool _validateTags(List<TagModel> tags) {
|
||||||
if (tags.isEmpty) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet();
|
final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet();
|
||||||
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
|
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<TagModel> tags) {
|
String? _getValidationError(List<TagModel> tags) {
|
||||||
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
|
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
|
final duplicateTags = tags
|
||||||
.map((tag) => tag.tag?.trim() ?? '')
|
.map((tag) => tag.tag?.trim() ?? '')
|
||||||
.fold<Map<String, int>>({}, (map, tag) {
|
.fold<Map<String, int>>({}, (map, tag) {
|
||||||
|
|||||||
@ -10,16 +10,16 @@ abstract class AssignTagModelEvent extends Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class InitializeTagModels extends AssignTagModelEvent {
|
class InitializeTagModels extends AssignTagModelEvent {
|
||||||
final List<TagModel>? initialTags;
|
final List<TagModel> initialTags;
|
||||||
final List<SelectedProduct> addedProducts;
|
final List<SelectedProduct> addedProducts;
|
||||||
|
|
||||||
const InitializeTagModels({
|
const InitializeTagModels({
|
||||||
required this.initialTags,
|
this.initialTags = const [],
|
||||||
required this.addedProducts,
|
required this.addedProducts,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [initialTags ?? [], addedProducts];
|
List<Object> get props => [initialTags, addedProducts];
|
||||||
}
|
}
|
||||||
|
|
||||||
class UpdateTag extends AssignTagModelEvent {
|
class UpdateTag extends AssignTagModelEvent {
|
||||||
|
|||||||
@ -5,7 +5,7 @@ abstract class AssignTagModelState extends Equatable {
|
|||||||
const AssignTagModelState();
|
const AssignTagModelState();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [];
|
List<Object?> get props => [];
|
||||||
}
|
}
|
||||||
|
|
||||||
class AssignTagModelInitial extends AssignTagModelState {}
|
class AssignTagModelInitial extends AssignTagModelState {}
|
||||||
@ -24,7 +24,7 @@ class AssignTagModelLoaded extends AssignTagModelState {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [tags, isSaveEnabled];
|
List<Object?> get props => [tags, isSaveEnabled, errorMessage];
|
||||||
}
|
}
|
||||||
|
|
||||||
class AssignTagModelError extends AssignTagModelState {
|
class AssignTagModelError extends AssignTagModelState {
|
||||||
@ -33,5 +33,5 @@ class AssignTagModelError extends AssignTagModelState {
|
|||||||
const AssignTagModelError(this.errorMessage);
|
const AssignTagModelError(this.errorMessage);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [errorMessage];
|
List<Object?> get props => [errorMessage];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/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/cancel_button.dart';
|
||||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||||
@ -11,40 +13,53 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/space_tem
|
|||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_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/space_model/widgets/dialog/create_space_model_dialog.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class AssignTagModelsDialog extends StatelessWidget {
|
class AssignTagModelsDialog extends StatelessWidget {
|
||||||
final List<ProductModel>? products;
|
final List<ProductModel>? products;
|
||||||
final List<SubspaceTemplateModel>? subspaces;
|
final List<SubspaceTemplateModel>? subspaces;
|
||||||
final List<TagModel>? initialTags;
|
final SpaceTemplateModel? spaceModel;
|
||||||
|
|
||||||
|
final List<TagModel> initialTags;
|
||||||
final ValueChanged<List<TagModel>>? onTagsAssigned;
|
final ValueChanged<List<TagModel>>? onTagsAssigned;
|
||||||
final List<SelectedProduct> addedProducts;
|
final List<SelectedProduct> addedProducts;
|
||||||
final List<String>? allTags;
|
final List<String>? allTags;
|
||||||
final String spaceName;
|
final String spaceName;
|
||||||
final String title;
|
final String title;
|
||||||
|
final BuildContext? pageContext;
|
||||||
|
final List<String>? otherSpaceModels;
|
||||||
|
|
||||||
const AssignTagModelsDialog({
|
const AssignTagModelsDialog(
|
||||||
Key? key,
|
{Key? key,
|
||||||
required this.products,
|
required this.products,
|
||||||
required this.subspaces,
|
required this.subspaces,
|
||||||
required this.addedProducts,
|
required this.addedProducts,
|
||||||
this.initialTags,
|
required this.initialTags,
|
||||||
this.onTagsAssigned,
|
this.onTagsAssigned,
|
||||||
this.allTags,
|
this.allTags,
|
||||||
required this.spaceName,
|
required this.spaceName,
|
||||||
required this.title
|
required this.title,
|
||||||
}) : super(key: key);
|
this.pageContext,
|
||||||
|
this.otherSpaceModels,
|
||||||
|
this.spaceModel})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final List<String> locations =
|
final List<String> locations = (subspaces ?? [])
|
||||||
(subspaces ?? []).map((subspace) => subspace.subspaceName).toList();
|
.map((subspace) => subspace.subspaceName)
|
||||||
|
.toList()
|
||||||
|
..add('Main Space');
|
||||||
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (_) => AssignTagModelBloc()
|
create: (_) => AssignTagModelBloc()
|
||||||
..add(InitializeTagModels(
|
..add(InitializeTagModels(
|
||||||
initialTags: initialTags,
|
initialTags: initialTags,
|
||||||
addedProducts: addedProducts,
|
addedProducts: addedProducts,
|
||||||
)),
|
)),
|
||||||
|
child: BlocListener<AssignTagModelBloc, AssignTagModelState>(
|
||||||
|
listener: (context, state) {},
|
||||||
child: BlocBuilder<AssignTagModelBloc, AssignTagModelState>(
|
child: BlocBuilder<AssignTagModelBloc, AssignTagModelState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is AssignTagModelLoaded) {
|
if (state is AssignTagModelLoaded) {
|
||||||
@ -72,20 +87,25 @@ class AssignTagModelsDialog extends StatelessWidget {
|
|||||||
columns: [
|
columns: [
|
||||||
DataColumn(
|
DataColumn(
|
||||||
label: Text('#',
|
label: Text('#',
|
||||||
style:
|
style: Theme.of(context)
|
||||||
Theme.of(context).textTheme.bodyMedium)),
|
.textTheme
|
||||||
|
.bodyMedium)),
|
||||||
DataColumn(
|
DataColumn(
|
||||||
label: Text('Device',
|
label: Text('Device',
|
||||||
style:
|
style: Theme.of(context)
|
||||||
Theme.of(context).textTheme.bodyMedium)),
|
.textTheme
|
||||||
|
.bodyMedium)),
|
||||||
DataColumn(
|
DataColumn(
|
||||||
|
numeric: false,
|
||||||
label: Text('Tag',
|
label: Text('Tag',
|
||||||
style:
|
style: Theme.of(context)
|
||||||
Theme.of(context).textTheme.bodyMedium)),
|
.textTheme
|
||||||
|
.bodyMedium)),
|
||||||
DataColumn(
|
DataColumn(
|
||||||
label: Text('Location',
|
label: Text('Location',
|
||||||
style:
|
style: Theme.of(context)
|
||||||
Theme.of(context).textTheme.bodyMedium)),
|
.textTheme
|
||||||
|
.bodyMedium)),
|
||||||
],
|
],
|
||||||
rows: state.tags.isEmpty
|
rows: state.tags.isEmpty
|
||||||
? [
|
? [
|
||||||
@ -96,7 +116,8 @@ class AssignTagModelsDialog extends StatelessWidget {
|
|||||||
'No Data Available',
|
'No Data Available',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: ColorsManager.lightGrayColor,
|
color:
|
||||||
|
ColorsManager.lightGrayColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -109,10 +130,12 @@ class AssignTagModelsDialog extends StatelessWidget {
|
|||||||
: List.generate(state.tags.length, (index) {
|
: List.generate(state.tags.length, (index) {
|
||||||
final tag = state.tags[index];
|
final tag = state.tags[index];
|
||||||
final controller = controllers[index];
|
final controller = controllers[index];
|
||||||
|
final availableTags = getAvailableTags(
|
||||||
|
allTags ?? [], state.tags, tag);
|
||||||
|
|
||||||
return DataRow(
|
return DataRow(
|
||||||
cells: [
|
cells: [
|
||||||
DataCell(Text(index.toString())),
|
DataCell(Text((index + 1).toString())),
|
||||||
DataCell(
|
DataCell(
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment:
|
mainAxisAlignment:
|
||||||
@ -123,147 +146,83 @@ class AssignTagModelsDialog extends StatelessWidget {
|
|||||||
tag.product?.name ?? 'Unknown',
|
tag.product?.name ?? 'Unknown',
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
)),
|
)),
|
||||||
IconButton(
|
const SizedBox(width: 10),
|
||||||
icon: const Icon(Icons.close,
|
Container(
|
||||||
color: ColorsManager.warningRed,
|
width: 20.0,
|
||||||
size: 16),
|
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: () {
|
onPressed: () {
|
||||||
context
|
context
|
||||||
.read<AssignTagModelBloc>()
|
.read<
|
||||||
|
AssignTagModelBloc>()
|
||||||
.add(DeleteTagModel(
|
.add(DeleteTagModel(
|
||||||
tagToDelete: tag,
|
tagToDelete: tag,
|
||||||
tags: state.tags));
|
tags: state.tags));
|
||||||
},
|
},
|
||||||
tooltip: 'Delete Tag',
|
tooltip: 'Delete Tag',
|
||||||
)
|
padding: EdgeInsets.zero,
|
||||||
|
constraints:
|
||||||
|
const BoxConstraints(),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
DataCell(
|
DataCell(
|
||||||
Row(
|
Container(
|
||||||
children: [
|
alignment: Alignment
|
||||||
Expanded(
|
.centerLeft, // Align cell content to the left
|
||||||
child: TextFormField(
|
child: SizedBox(
|
||||||
controller: controller,
|
width: double
|
||||||
onChanged: (value) {
|
.infinity, // Ensure full width for dropdown
|
||||||
context
|
child: DialogTextfieldDropdown(
|
||||||
.read<AssignTagModelBloc>()
|
items: availableTags,
|
||||||
.add(UpdateTag(
|
initialValue: tag.tag,
|
||||||
index: index,
|
|
||||||
tag: value.trim(),
|
|
||||||
));
|
|
||||||
},
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
hintText: 'Enter Tag',
|
|
||||||
border: InputBorder.none,
|
|
||||||
),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: ColorsManager.blackColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: MediaQuery.of(context)
|
|
||||||
.size
|
|
||||||
.width *
|
|
||||||
0.15,
|
|
||||||
child: PopupMenuButton<String>(
|
|
||||||
color: ColorsManager.whiteColors,
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.arrow_drop_down,
|
|
||||||
color: ColorsManager.blackColor),
|
|
||||||
onSelected: (value) {
|
onSelected: (value) {
|
||||||
controller.text = value;
|
controller.text = value;
|
||||||
context
|
context
|
||||||
.read<AssignTagModelBloc>()
|
.read<
|
||||||
|
AssignTagModelBloc>()
|
||||||
.add(UpdateTag(
|
.add(UpdateTag(
|
||||||
index: index,
|
index: index,
|
||||||
tag: value,
|
tag: value,
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
itemBuilder: (context) {
|
|
||||||
return (allTags ?? [])
|
|
||||||
.where((tagValue) => !state
|
|
||||||
.tags
|
|
||||||
.map((e) => e.tag)
|
|
||||||
.contains(tagValue))
|
|
||||||
.map((tagValue) {
|
|
||||||
return PopupMenuItem<String>(
|
|
||||||
textStyle: const TextStyle(
|
|
||||||
color: ColorsManager
|
|
||||||
.textPrimaryColor),
|
|
||||||
value: tagValue,
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints:
|
|
||||||
BoxConstraints(
|
|
||||||
minWidth: MediaQuery.of(
|
|
||||||
context)
|
|
||||||
.size
|
|
||||||
.width *
|
|
||||||
0.15,
|
|
||||||
maxWidth: MediaQuery.of(
|
|
||||||
context)
|
|
||||||
.size
|
|
||||||
.width *
|
|
||||||
0.15,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
tagValue,
|
|
||||||
overflow: TextOverflow
|
|
||||||
.ellipsis,
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}).toList();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
DataCell(
|
DataCell(
|
||||||
DropdownButtonHideUnderline(
|
SizedBox(
|
||||||
child: DropdownButton<String>(
|
width: double.infinity,
|
||||||
value: tag.location ?? 'None',
|
child: DialogDropdown(
|
||||||
dropdownColor: ColorsManager
|
items: locations,
|
||||||
.whiteColors, // Dropdown background
|
selectedValue:
|
||||||
style: const TextStyle(
|
tag.location ?? 'Main Space',
|
||||||
color: Colors
|
onSelected: (value) {
|
||||||
.black), // Style for selected text
|
|
||||||
items: [
|
|
||||||
const DropdownMenuItem<String>(
|
|
||||||
value: 'None',
|
|
||||||
child: Text(
|
|
||||||
'None',
|
|
||||||
style: TextStyle(
|
|
||||||
color: ColorsManager
|
|
||||||
.textPrimaryColor),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
...locations.map((location) {
|
|
||||||
return DropdownMenuItem<String>(
|
|
||||||
value: location,
|
|
||||||
child: Text(
|
|
||||||
location,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: ColorsManager
|
|
||||||
.textPrimaryColor),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
],
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value != null) {
|
|
||||||
context
|
context
|
||||||
.read<AssignTagModelBloc>()
|
.read<
|
||||||
|
AssignTagModelBloc>()
|
||||||
.add(UpdateLocation(
|
.add(UpdateLocation(
|
||||||
index: index,
|
index: index,
|
||||||
location: value,
|
location: value,
|
||||||
));
|
));
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
)),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -273,7 +232,8 @@ class AssignTagModelsDialog extends StatelessWidget {
|
|||||||
if (state.errorMessage != null)
|
if (state.errorMessage != null)
|
||||||
Text(
|
Text(
|
||||||
state.errorMessage!,
|
state.errorMessage!,
|
||||||
style: const TextStyle(color: ColorsManager.warningRed),
|
style: const TextStyle(
|
||||||
|
color: ColorsManager.warningRed),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -284,25 +244,68 @@ class AssignTagModelsDialog extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: CancelButton(
|
child: Builder(
|
||||||
label: 'Cancel',
|
builder: (buttonContext) => CancelButton(
|
||||||
|
label: 'Add New Device',
|
||||||
onPressed: () async {
|
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) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
await showDialog(
|
|
||||||
|
await showDialog<bool>(
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => CreateSpaceModelDialog(
|
builder: (dialogContext) =>
|
||||||
|
AddDeviceTypeModelWidget(
|
||||||
products: products,
|
products: products,
|
||||||
|
subspaces: subspaces,
|
||||||
|
isCreate: false,
|
||||||
|
initialSelectedProducts:
|
||||||
|
addedProducts,
|
||||||
allTags: allTags,
|
allTags: allTags,
|
||||||
|
spaceName: spaceName,
|
||||||
|
otherSpaceModels: otherSpaceModels,
|
||||||
|
spaceTagModels: state.tags,
|
||||||
|
pageContext: pageContext,
|
||||||
spaceModel: SpaceTemplateModel(
|
spaceModel: SpaceTemplateModel(
|
||||||
modelName: spaceName,
|
modelName: spaceName,
|
||||||
subspaceModels: subspaces,
|
tags: state.tags,
|
||||||
tags: initialTags),
|
uuid: spaceModel?.uuid,
|
||||||
),
|
internalId:
|
||||||
|
spaceModel?.internalId,
|
||||||
|
subspaceModels: subspaces)),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
@ -313,34 +316,53 @@ class AssignTagModelsDialog extends StatelessWidget {
|
|||||||
foregroundColor: ColorsManager.whiteColors,
|
foregroundColor: ColorsManager.whiteColors,
|
||||||
onPressed: state.isSaveEnabled
|
onPressed: state.isSaveEnabled
|
||||||
? () async {
|
? () async {
|
||||||
Navigator.of(context).pop();
|
|
||||||
final assignedTags = <TagModel>{};
|
|
||||||
for (var tag in state.tags) {
|
for (var tag in state.tags) {
|
||||||
if (tag.location == null ||
|
if (tag.location == null ||
|
||||||
subspaces == null) {
|
subspaces == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (var subspace in subspaces!) {
|
|
||||||
if (tag.location == subspace.subspaceName) {
|
final previousTagSubspace =
|
||||||
subspace.tags ??= [];
|
checkTagExistInSubspace(
|
||||||
subspace.tags!.add(tag);
|
tag, subspaces ?? []);
|
||||||
assignedTags.add(tag);
|
|
||||||
break;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Navigator.of(context)
|
||||||
state.tags.removeWhere(assignedTags.contains);
|
.popUntil((route) => route.isFirst);
|
||||||
|
|
||||||
await showDialog(
|
await showDialog(
|
||||||
barrierDismissible: false,
|
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => CreateSpaceModelDialog(
|
builder: (BuildContext dialogContext) {
|
||||||
|
return CreateSpaceModelDialog(
|
||||||
products: products,
|
products: products,
|
||||||
allTags: allTags,
|
allTags: allTags,
|
||||||
|
pageContext: pageContext,
|
||||||
|
otherSpaceModels: otherSpaceModels,
|
||||||
spaceModel: SpaceTemplateModel(
|
spaceModel: SpaceTemplateModel(
|
||||||
modelName: spaceName,
|
modelName: spaceName,
|
||||||
subspaceModels: subspaces,
|
tags: state.tags,
|
||||||
tags: state.tags),
|
uuid: spaceModel?.uuid,
|
||||||
),
|
internalId:
|
||||||
|
spaceModel?.internalId,
|
||||||
|
subspaceModels: subspaces),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
@ -359,6 +381,52 @@ class AssignTagModelsDialog extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> getAvailableTags(
|
||||||
|
List<String> allTags, List<TagModel> currentTags, TagModel currentTag) {
|
||||||
|
return allTags
|
||||||
|
.where((tagValue) => !currentTags
|
||||||
|
.where((e) => e != currentTag) // Exclude the current row
|
||||||
|
.map((e) => e.tag)
|
||||||
|
.contains(tagValue))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeTagFromSubspace(TagModel tag, SubspaceTemplateModel? subspace) {
|
||||||
|
subspace?.tags?.removeWhere((t) => t.internalId == tag.internalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubspaceTemplateModel? checkTagExistInSubspace(
|
||||||
|
TagModel tag, List<SubspaceTemplateModel>? subspaces) {
|
||||||
|
if (subspaces == null) return null;
|
||||||
|
for (var subspace in subspaces) {
|
||||||
|
if (subspace.tags == null) return null;
|
||||||
|
for (var t in subspace.tags!) {
|
||||||
|
if (tag.internalId == t.internalId) return subspace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void moveToNewSubspace(TagModel tag, List<SubspaceTemplateModel> subspaces) {
|
||||||
|
final targetSubspace = subspaces
|
||||||
|
.firstWhere((subspace) => subspace.subspaceName == tag.location);
|
||||||
|
|
||||||
|
targetSubspace.tags ??= [];
|
||||||
|
if (targetSubspace.tags?.any((t) => t.internalId == tag.internalId) !=
|
||||||
|
true) {
|
||||||
|
targetSubspace.tags?.add(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateTagInSubspace(TagModel tag, SubspaceTemplateModel? subspace) {
|
||||||
|
final currentTag = subspace?.tags?.firstWhere(
|
||||||
|
(t) => t.internalId == tag.internalId,
|
||||||
);
|
);
|
||||||
|
if (currentTag != null) {
|
||||||
|
currentTag.tag = tag.tag;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,57 @@
|
|||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||||
|
import 'package:syncrow_web/utils/constants/action_enum.dart';
|
||||||
|
|
||||||
|
import 'subspace_event.dart';
|
||||||
|
import 'subspace_state.dart';
|
||||||
|
|
||||||
|
class SubSpaceBloc extends Bloc<SubSpaceEvent, SubSpaceState> {
|
||||||
|
SubSpaceBloc() : super(SubSpaceState([], [], '')) {
|
||||||
|
on<AddSubSpace>((event, emit) {
|
||||||
|
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 updatedSubSpaces = List<SubspaceModel>.from(state.subSpaces)
|
||||||
|
..add(event.subSpace);
|
||||||
|
|
||||||
|
emit(SubSpaceState(
|
||||||
|
updatedSubSpaces,
|
||||||
|
state.updatedSubSpaceModels,
|
||||||
|
'',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle RemoveSubSpace Event
|
||||||
|
on<RemoveSubSpace>((event, emit) {
|
||||||
|
final updatedSubSpaces = List<SubspaceModel>.from(state.subSpaces)
|
||||||
|
..remove(event.subSpace);
|
||||||
|
|
||||||
|
final updatedSubspaceModels = List<UpdateSubspaceModel>.from(
|
||||||
|
state.updatedSubSpaceModels,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (event.subSpace.uuid?.isNotEmpty ?? false) {
|
||||||
|
updatedSubspaceModels.add(UpdateSubspaceModel(
|
||||||
|
action: Action.delete,
|
||||||
|
uuid: event.subSpace.uuid!,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(SubSpaceState(
|
||||||
|
updatedSubSpaces,
|
||||||
|
updatedSubspaceModels,
|
||||||
|
'', // Clear error message
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle UpdateSubSpace Event
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||||
|
|
||||||
|
abstract class SubSpaceEvent {}
|
||||||
|
|
||||||
|
class AddSubSpace extends SubSpaceEvent {
|
||||||
|
final SubspaceModel subSpace;
|
||||||
|
AddSubSpace(this.subSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoveSubSpace extends SubSpaceEvent {
|
||||||
|
final SubspaceModel subSpace;
|
||||||
|
RemoveSubSpace(this.subSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateSubSpace extends SubSpaceEvent {
|
||||||
|
final SubspaceModel updatedSubSpace;
|
||||||
|
UpdateSubSpace(this.updatedSubSpace);
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||||
|
|
||||||
|
class SubSpaceState {
|
||||||
|
final List<SubspaceModel> subSpaces;
|
||||||
|
final List<UpdateSubspaceModel> updatedSubSpaceModels;
|
||||||
|
final String errorMessage;
|
||||||
|
|
||||||
|
SubSpaceState(
|
||||||
|
this.subSpaces,
|
||||||
|
this.updatedSubSpaceModels,
|
||||||
|
this.errorMessage,
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
SubSpaceState copyWith({
|
||||||
|
List<SubspaceModel>? subSpaces,
|
||||||
|
List<UpdateSubspaceModel>? updatedSubSpaceModels,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return SubSpaceState(
|
||||||
|
subSpaces ?? this.subSpaces,
|
||||||
|
updatedSubSpaceModels ?? this.updatedSubSpaceModels,
|
||||||
|
errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,196 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
|
||||||
|
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_event.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/create_subspace/bloc/subspace_state.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class CreateSubSpaceDialog extends StatelessWidget {
|
||||||
|
final bool isEdit;
|
||||||
|
final String dialogTitle;
|
||||||
|
final List<SubspaceModel>? existingSubSpaces;
|
||||||
|
final String? spaceName;
|
||||||
|
final List<Tag>? spaceTags;
|
||||||
|
final List<ProductModel>? products;
|
||||||
|
final Function(List<SubspaceModel>?)? onSave;
|
||||||
|
|
||||||
|
const CreateSubSpaceDialog(
|
||||||
|
{Key? key,
|
||||||
|
required this.isEdit,
|
||||||
|
required this.dialogTitle,
|
||||||
|
this.existingSubSpaces,
|
||||||
|
required this.spaceName,
|
||||||
|
required this.spaceTags,
|
||||||
|
required this.products,
|
||||||
|
required this.onSave})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
final textController = TextEditingController();
|
||||||
|
|
||||||
|
return Dialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: BlocProvider(
|
||||||
|
create: (_) {
|
||||||
|
final bloc = SubSpaceBloc();
|
||||||
|
if (existingSubSpaces != null) {
|
||||||
|
for (var subSpace in existingSubSpaces!) {
|
||||||
|
bloc.add(AddSubSpace(subSpace));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bloc;
|
||||||
|
},
|
||||||
|
child: BlocBuilder<SubSpaceBloc, SubSpaceState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Container(
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
child: SizedBox(
|
||||||
|
width: screenWidth * 0.35,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
dialogTitle,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.headlineLarge
|
||||||
|
?.copyWith(color: ColorsManager.blackColor),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Container(
|
||||||
|
width: screenWidth * 0.35,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 10.0, horizontal: 16.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.boxColor,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 8.0,
|
||||||
|
runSpacing: 8.0,
|
||||||
|
children: [
|
||||||
|
...state.subSpaces.map(
|
||||||
|
(subSpace) => Chip(
|
||||||
|
label: Text(
|
||||||
|
subSpace.subspaceName,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorsManager.spaceColor),
|
||||||
|
),
|
||||||
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
side: const BorderSide(
|
||||||
|
color: ColorsManager.transparentColor,
|
||||||
|
width: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
deleteIcon: Container(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorsManager.lightGrayColor,
|
||||||
|
width: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 16,
|
||||||
|
color: ColorsManager.lightGrayColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onDeleted: () => context
|
||||||
|
.read<SubSpaceBloc>()
|
||||||
|
.add(RemoveSubSpace(subSpace)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 200,
|
||||||
|
child: TextField(
|
||||||
|
controller: textController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
hintText: state.subSpaces.isEmpty
|
||||||
|
? 'Please enter the name'
|
||||||
|
: null,
|
||||||
|
hintStyle: const TextStyle(
|
||||||
|
color: ColorsManager.lightGrayColor),
|
||||||
|
),
|
||||||
|
onSubmitted: (value) {
|
||||||
|
if (value.trim().isNotEmpty) {
|
||||||
|
context.read<SubSpaceBloc>().add(
|
||||||
|
AddSubSpace(SubspaceModel(
|
||||||
|
subspaceName: value.trim(),
|
||||||
|
disabled: false)));
|
||||||
|
textController.clear();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorsManager.blackColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (state.errorMessage.isNotEmpty)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
|
child: Text(
|
||||||
|
state.errorMessage,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorsManager.warningRed,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: CancelButton(
|
||||||
|
label: 'Cancel',
|
||||||
|
onPressed: () async {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: DefaultButton(
|
||||||
|
onPressed: () async {
|
||||||
|
final subSpaces = context
|
||||||
|
.read<SubSpaceBloc>()
|
||||||
|
.state
|
||||||
|
.subSpaces;
|
||||||
|
onSave!(subSpaces);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
backgroundColor: ColorsManager.secondaryColor,
|
||||||
|
borderRadius: 10,
|
||||||
|
foregroundColor: ColorsManager.whiteColors,
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,19 +6,23 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_
|
|||||||
import 'package:syncrow_web/utils/constants/action_enum.dart';
|
import 'package:syncrow_web/utils/constants/action_enum.dart';
|
||||||
|
|
||||||
class SubSpaceModelBloc extends Bloc<SubSpaceModelEvent, SubSpaceModelState> {
|
class SubSpaceModelBloc extends Bloc<SubSpaceModelEvent, SubSpaceModelState> {
|
||||||
SubSpaceModelBloc() : super(SubSpaceModelState([], [], '')) {
|
SubSpaceModelBloc() : super(SubSpaceModelState([], [], '', {})) {
|
||||||
// Handle AddSubSpaceModel Event
|
// Handle AddSubSpaceModel Event
|
||||||
on<AddSubSpaceModel>((event, emit) {
|
on<AddSubSpaceModel>((event, emit) {
|
||||||
// Check for duplicate names (case-insensitive)
|
|
||||||
final existingNames =
|
final existingNames =
|
||||||
state.subSpaces.map((e) => e.subspaceName.toLowerCase()).toSet();
|
state.subSpaces.map((e) => e.subspaceName.toLowerCase()).toSet();
|
||||||
|
|
||||||
if (existingNames.contains(event.subSpace.subspaceName.toLowerCase())) {
|
if (existingNames.contains(event.subSpace.subspaceName.toLowerCase())) {
|
||||||
// Emit state with an error message if duplicate name exists
|
final updatedDuplicates = Set<String>.from(state.duplicates)
|
||||||
|
..add(event.subSpace.subspaceName.toLowerCase());
|
||||||
|
final updatedSubSpaces =
|
||||||
|
List<SubspaceTemplateModel>.from(state.subSpaces)
|
||||||
|
..add(event.subSpace);
|
||||||
emit(SubSpaceModelState(
|
emit(SubSpaceModelState(
|
||||||
state.subSpaces,
|
updatedSubSpaces,
|
||||||
state.updatedSubSpaceModels,
|
state.updatedSubSpaceModels,
|
||||||
'Subspace name already exists.',
|
'*Duplicated sub-space name',
|
||||||
|
updatedDuplicates,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
// Add subspace if no duplicate exists
|
// Add subspace if no duplicate exists
|
||||||
@ -29,7 +33,9 @@ class SubSpaceModelBloc extends Bloc<SubSpaceModelEvent, SubSpaceModelState> {
|
|||||||
emit(SubSpaceModelState(
|
emit(SubSpaceModelState(
|
||||||
updatedSubSpaces,
|
updatedSubSpaces,
|
||||||
state.updatedSubSpaceModels,
|
state.updatedSubSpaceModels,
|
||||||
'', // Clear error message
|
'',
|
||||||
|
state.duplicates,
|
||||||
|
// Clear error message
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -42,6 +48,16 @@ class SubSpaceModelBloc extends Bloc<SubSpaceModelEvent, SubSpaceModelState> {
|
|||||||
final updatedSubspaceModels = List<UpdateSubspaceTemplateModel>.from(
|
final updatedSubspaceModels = List<UpdateSubspaceTemplateModel>.from(
|
||||||
state.updatedSubSpaceModels,
|
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) {
|
if (event.subSpace.uuid?.isNotEmpty ?? false) {
|
||||||
updatedSubspaceModels.add(UpdateSubspaceTemplateModel(
|
updatedSubspaceModels.add(UpdateSubspaceTemplateModel(
|
||||||
@ -53,7 +69,9 @@ class SubSpaceModelBloc extends Bloc<SubSpaceModelEvent, SubSpaceModelState> {
|
|||||||
emit(SubSpaceModelState(
|
emit(SubSpaceModelState(
|
||||||
updatedSubSpaces,
|
updatedSubSpaces,
|
||||||
updatedSubspaceModels,
|
updatedSubspaceModels,
|
||||||
'', // Clear error message
|
'',
|
||||||
|
updatedDuplicates,
|
||||||
|
// Clear error message
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -78,7 +96,9 @@ class SubSpaceModelBloc extends Bloc<SubSpaceModelEvent, SubSpaceModelState> {
|
|||||||
emit(SubSpaceModelState(
|
emit(SubSpaceModelState(
|
||||||
updatedSubSpaces,
|
updatedSubSpaces,
|
||||||
updatedSubspaceModels,
|
updatedSubspaceModels,
|
||||||
'', // Clear error message
|
'',
|
||||||
|
state.duplicates,
|
||||||
|
// Clear error message
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,22 +5,26 @@ class SubSpaceModelState {
|
|||||||
final List<SubspaceTemplateModel> subSpaces;
|
final List<SubspaceTemplateModel> subSpaces;
|
||||||
final List<UpdateSubspaceTemplateModel> updatedSubSpaceModels;
|
final List<UpdateSubspaceTemplateModel> updatedSubSpaceModels;
|
||||||
final String errorMessage;
|
final String errorMessage;
|
||||||
|
final Set<String> duplicates;
|
||||||
|
|
||||||
SubSpaceModelState(
|
SubSpaceModelState(
|
||||||
this.subSpaces,
|
this.subSpaces,
|
||||||
this.updatedSubSpaceModels,
|
this.updatedSubSpaceModels,
|
||||||
this.errorMessage,
|
this.errorMessage,
|
||||||
|
this.duplicates,
|
||||||
);
|
);
|
||||||
|
|
||||||
SubSpaceModelState copyWith({
|
SubSpaceModelState copyWith({
|
||||||
List<SubspaceTemplateModel>? subSpaces,
|
List<SubspaceTemplateModel>? subSpaces,
|
||||||
List<UpdateSubspaceTemplateModel>? updatedSubSpaceModels,
|
List<UpdateSubspaceTemplateModel>? updatedSubSpaceModels,
|
||||||
String? errorMessage,
|
String? errorMessage,
|
||||||
|
Set<String>? duplicates,
|
||||||
}) {
|
}) {
|
||||||
return SubSpaceModelState(
|
return SubSpaceModelState(
|
||||||
subSpaces ?? this.subSpaces,
|
subSpaces ?? this.subSpaces,
|
||||||
updatedSubSpaceModels ?? this.updatedSubSpaceModels,
|
updatedSubSpaceModels ?? this.updatedSubSpaceModels,
|
||||||
errorMessage ?? this.errorMessage,
|
errorMessage ?? this.errorMessage,
|
||||||
|
duplicates ?? this.duplicates,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,37 +2,25 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
|
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
|
||||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
|
||||||
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart';
|
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart';
|
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_event.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart';
|
import 'package:syncrow_web/pages/spaces_management/create_subspace_model/bloc/subspace_model_state.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart';
|
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class CreateSubSpaceModelDialog extends StatelessWidget {
|
class CreateSubSpaceModelDialog extends StatelessWidget {
|
||||||
final bool isEdit;
|
final bool isEdit;
|
||||||
final String dialogTitle;
|
final String dialogTitle;
|
||||||
final List<SubspaceTemplateModel>? existingSubSpaces;
|
final List<SubspaceTemplateModel>? existingSubSpaces;
|
||||||
final String? spaceName;
|
final void Function(List<SubspaceTemplateModel> newSubspaces)? onUpdate;
|
||||||
final List<TagModel>? spaceTagModels;
|
|
||||||
final List<String>? allTags;
|
|
||||||
final List<ProductModel>? products;
|
|
||||||
final SpaceTemplateModel? spaceModel;
|
|
||||||
|
|
||||||
const CreateSubSpaceModelDialog({
|
const CreateSubSpaceModelDialog(
|
||||||
Key? key,
|
{Key? key,
|
||||||
required this.isEdit,
|
required this.isEdit,
|
||||||
required this.dialogTitle,
|
required this.dialogTitle,
|
||||||
this.existingSubSpaces,
|
this.existingSubSpaces,
|
||||||
required this.allTags,
|
this.onUpdate})
|
||||||
required this.spaceName,
|
: super(key: key);
|
||||||
required this.spaceTagModels,
|
|
||||||
required this.products,
|
|
||||||
required this.spaceModel,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -58,7 +46,7 @@ class CreateSubSpaceModelDialog extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
color: ColorsManager.whiteColors,
|
color: ColorsManager.whiteColors,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: screenWidth * 0.35,
|
width: screenWidth * 0.3,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -85,18 +73,40 @@ class CreateSubSpaceModelDialog extends StatelessWidget {
|
|||||||
spacing: 8.0,
|
spacing: 8.0,
|
||||||
runSpacing: 8.0,
|
runSpacing: 8.0,
|
||||||
children: [
|
children: [
|
||||||
...state.subSpaces.map(
|
...state.subSpaces.asMap().entries.map(
|
||||||
(subSpace) => Chip(
|
(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(
|
label: Text(
|
||||||
subSpace.subspaceName,
|
subSpace.subspaceName,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: ColorsManager.spaceColor),
|
color: ColorsManager.spaceColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
backgroundColor: ColorsManager.whiteColors,
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(10),
|
||||||
side: const BorderSide(
|
side: BorderSide(
|
||||||
color: ColorsManager.transparentColor,
|
color: isDuplicate
|
||||||
|
? ColorsManager.red
|
||||||
|
: ColorsManager.transparentColor,
|
||||||
width: 0,
|
width: 0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -119,7 +129,8 @@ class CreateSubSpaceModelDialog extends StatelessWidget {
|
|||||||
onDeleted: () => context
|
onDeleted: () => context
|
||||||
.read<SubSpaceModelBloc>()
|
.read<SubSpaceModelBloc>()
|
||||||
.add(RemoveSubSpaceModel(subSpace)),
|
.add(RemoveSubSpaceModel(subSpace)),
|
||||||
),
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 200,
|
width: 200,
|
||||||
@ -147,20 +158,20 @@ class CreateSubSpaceModelDialog extends StatelessWidget {
|
|||||||
color: ColorsManager.blackColor),
|
color: ColorsManager.blackColor),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
if (state.errorMessage.isNotEmpty)
|
if (state.errorMessage.isNotEmpty)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
padding: const EdgeInsets.only(bottom: 16.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
state.errorMessage,
|
state.errorMessage,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: ColorsManager.warningRed,
|
color: ColorsManager.red,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@ -169,51 +180,29 @@ class CreateSubSpaceModelDialog extends StatelessWidget {
|
|||||||
label: 'Cancel',
|
label: 'Cancel',
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
await showDialog(
|
|
||||||
barrierDismissible: false,
|
|
||||||
context: context,
|
|
||||||
builder: (context) =>
|
|
||||||
CreateSpaceModelDialog(
|
|
||||||
products: products,
|
|
||||||
allTags: allTags,
|
|
||||||
spaceModel: SpaceTemplateModel(
|
|
||||||
modelName: spaceName ?? '',
|
|
||||||
subspaceModels: existingSubSpaces,
|
|
||||||
tags: spaceTagModels,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
onPressed: () async {
|
onPressed: (state.errorMessage.isNotEmpty)
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
final subSpaces = context
|
final subSpaces = context
|
||||||
.read<SubSpaceModelBloc>()
|
.read<SubSpaceModelBloc>()
|
||||||
.state
|
.state
|
||||||
.subSpaces;
|
.subSpaces;
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
if (onUpdate != null) {
|
||||||
await showDialog(
|
onUpdate!(subSpaces);
|
||||||
barrierDismissible: false,
|
}
|
||||||
context: context,
|
|
||||||
builder: (context) =>
|
|
||||||
CreateSpaceModelDialog(
|
|
||||||
products: products,
|
|
||||||
allTags: allTags,
|
|
||||||
spaceModel: SpaceTemplateModel(
|
|
||||||
modelName: spaceName ?? '',
|
|
||||||
subspaceModels: subSpaces,
|
|
||||||
tags: spaceTagModels,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
backgroundColor: ColorsManager.secondaryColor,
|
backgroundColor: ColorsManager.secondaryColor,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
foregroundColor: ColorsManager.whiteColors,
|
foregroundColor: state.errorMessage.isNotEmpty
|
||||||
|
? ColorsManager.whiteColorsWithOpacity
|
||||||
|
: ColorsManager.whiteColors,
|
||||||
child: const Text('OK'),
|
child: const Text('OK'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
81
lib/pages/spaces_management/helper/tag_helper.dart
Normal file
81
lib/pages/spaces_management/helper/tag_helper.dart
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
||||||
|
|
||||||
|
class TagHelper {
|
||||||
|
static 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) {
|
||||||
|
for (var existingTag in subspace.tags!) {
|
||||||
|
initialTags.addAll(
|
||||||
|
subspace.tags!.map(
|
||||||
|
(tag) => tag.copyWith(
|
||||||
|
location: subspace.subspaceName,
|
||||||
|
internalId: existingTag.internalId,
|
||||||
|
tag: existingTag.tag),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return initialTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<ProductModel, int> groupTags(List<TagModel> tags) {
|
||||||
|
final Map<ProductModel, int> groupedTags = {};
|
||||||
|
for (var tag in tags) {
|
||||||
|
if (tag.product != null) {
|
||||||
|
final product = tag.product!;
|
||||||
|
groupedTags[product] = (groupedTags[product] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groupedTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<SelectedProduct> createInitialSelectedProducts(
|
||||||
|
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces) {
|
||||||
|
final Map<ProductModel, int> productCounts = {};
|
||||||
|
|
||||||
|
if (tags != null) {
|
||||||
|
for (var tag in tags) {
|
||||||
|
if (tag.product != null) {
|
||||||
|
productCounts[tag.product!] = (productCounts[tag.product!] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subspaces != null) {
|
||||||
|
for (var subspace in subspaces) {
|
||||||
|
if (subspace.tags != null) {
|
||||||
|
for (var tag in subspace.tags!) {
|
||||||
|
if (tag.product != null) {
|
||||||
|
productCounts[tag.product!] =
|
||||||
|
(productCounts[tag.product!] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return productCounts.entries
|
||||||
|
.map((entry) => SelectedProduct(
|
||||||
|
productId: entry.key.uuid,
|
||||||
|
count: entry.value,
|
||||||
|
productName: entry.key.name ?? 'Unnamed',
|
||||||
|
product: entry.key,
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_state.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> {
|
||||||
|
SpaceModelBloc() : super(SpaceModelInitial()) {
|
||||||
|
on<SpaceModelSelectedEvent>((event, emit) {
|
||||||
|
emit(SpaceModelSelectedState(event.selectedIndex));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
abstract class SpaceModelEvent {}
|
||||||
|
|
||||||
|
class SpaceModelSelectedEvent extends SpaceModelEvent {
|
||||||
|
final int selectedIndex;
|
||||||
|
|
||||||
|
SpaceModelSelectedEvent(this.selectedIndex);
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
abstract class SpaceModelState {}
|
||||||
|
|
||||||
|
class SpaceModelInitial extends SpaceModelState {}
|
||||||
|
|
||||||
|
class SpaceModelSelectedState extends SpaceModelState {
|
||||||
|
final int selectedIndex;
|
||||||
|
|
||||||
|
SpaceModelSelectedState(this.selectedIndex);
|
||||||
|
}
|
||||||
@ -0,0 +1,130 @@
|
|||||||
|
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/link_space_model/bloc/link_space_model_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_model_event.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_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/space_model_card_widget.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class LinkSpaceModelDialog extends StatelessWidget {
|
||||||
|
final Function(SpaceTemplateModel?)? onSave;
|
||||||
|
final int? initialSelectedIndex;
|
||||||
|
final List<SpaceTemplateModel> spaceModels;
|
||||||
|
|
||||||
|
const LinkSpaceModelDialog({
|
||||||
|
Key? key,
|
||||||
|
this.onSave,
|
||||||
|
this.initialSelectedIndex,
|
||||||
|
required this.spaceModels,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (context) => SpaceModelBloc()
|
||||||
|
..add(
|
||||||
|
SpaceModelSelectedEvent(initialSelectedIndex ?? -1),
|
||||||
|
),
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) {
|
||||||
|
final bloc = context.read<SpaceModelBloc>();
|
||||||
|
return AlertDialog(
|
||||||
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
|
title: const Text('Link a space model'),
|
||||||
|
content: spaceModels.isNotEmpty
|
||||||
|
? Container(
|
||||||
|
color: ColorsManager.textFieldGreyColor,
|
||||||
|
width: MediaQuery.of(context).size.width * 0.7,
|
||||||
|
height: MediaQuery.of(context).size.height * 0.6,
|
||||||
|
child: BlocBuilder<SpaceModelBloc, SpaceModelState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
int selectedIndex = -1;
|
||||||
|
if (state is SpaceModelSelectedState) {
|
||||||
|
selectedIndex = state.selectedIndex;
|
||||||
|
}
|
||||||
|
return GridView.builder(
|
||||||
|
gridDelegate:
|
||||||
|
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 2,
|
||||||
|
crossAxisSpacing: 10.0,
|
||||||
|
mainAxisSpacing: 10.0,
|
||||||
|
childAspectRatio: 3,
|
||||||
|
),
|
||||||
|
itemCount: spaceModels.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
final model = spaceModels[index];
|
||||||
|
final isSelected = selectedIndex == index;
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
bloc.add(SpaceModelSelectedEvent(index));
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.all(10.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: isSelected
|
||||||
|
? ColorsManager.spaceColor
|
||||||
|
: Colors.transparent,
|
||||||
|
width: 2.0,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
|
),
|
||||||
|
child: SpaceModelCardWidget(model: model,),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Text('No space models available.'),
|
||||||
|
actions: [
|
||||||
|
Center(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
CancelButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
label: 'Cancel',
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
BlocBuilder<SpaceModelBloc, SpaceModelState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final isEnabled = state is SpaceModelSelectedState &&
|
||||||
|
state.selectedIndex >= 0;
|
||||||
|
return SizedBox(
|
||||||
|
width: 140,
|
||||||
|
child: DefaultButton(
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 10,
|
||||||
|
onPressed: isEnabled
|
||||||
|
? () {
|
||||||
|
if (onSave != null) {
|
||||||
|
final selectedModel =
|
||||||
|
spaceModels[state.selectedIndex];
|
||||||
|
|
||||||
|
onSave!(selectedModel);
|
||||||
|
}
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
child: const Text('Save'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,8 +3,11 @@ import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_spac
|
|||||||
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/create_space_model_state.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||||
|
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_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/services/space_model_mang_api.dart';
|
||||||
|
import 'package:syncrow_web/utils/constants/action_enum.dart';
|
||||||
|
|
||||||
class CreateSpaceModelBloc
|
class CreateSpaceModelBloc
|
||||||
extends Bloc<CreateSpaceModelEvent, CreateSpaceModelState> {
|
extends Bloc<CreateSpaceModelEvent, CreateSpaceModelState> {
|
||||||
@ -67,28 +70,116 @@ class CreateSpaceModelBloc
|
|||||||
_space = event.spaceTemplate;
|
_space = event.spaceTemplate;
|
||||||
emit(CreateSpaceModelLoaded(_space!));
|
emit(CreateSpaceModelLoaded(_space!));
|
||||||
});
|
});
|
||||||
|
|
||||||
on<AddSubspacesToSpaceTemplate>((event, emit) {
|
on<AddSubspacesToSpaceTemplate>((event, emit) {
|
||||||
final currentState = state;
|
final currentState = state;
|
||||||
|
|
||||||
if (currentState is CreateSpaceModelLoaded) {
|
if (currentState is CreateSpaceModelLoaded) {
|
||||||
final updatedSpace = currentState.space.copyWith(
|
final eventSubspaceIds =
|
||||||
subspaceModels: [
|
event.subspaces.map((e) => e.internalId).toSet();
|
||||||
...(_space!.subspaceModels ?? []),
|
|
||||||
...event.subspaces,
|
// Update or retain subspaces
|
||||||
],
|
final updatedSubspaces = currentState.space.subspaceModels
|
||||||
|
?.where((subspace) =>
|
||||||
|
eventSubspaceIds.contains(subspace.internalId))
|
||||||
|
.map((subspace) {
|
||||||
|
final matchingEventSubspace = event.subspaces.firstWhere(
|
||||||
|
(e) => e.internalId == subspace.internalId,
|
||||||
|
orElse: () => subspace,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Update the subspace's tags
|
||||||
|
final eventTagIds = matchingEventSubspace.tags
|
||||||
|
?.map((e) => e.internalId)
|
||||||
|
.toSet() ??
|
||||||
|
{};
|
||||||
|
|
||||||
|
final updatedTags = [
|
||||||
|
...?subspace.tags?.map<TagModel>((tag) {
|
||||||
|
final matchingTag =
|
||||||
|
matchingEventSubspace.tags?.firstWhere(
|
||||||
|
(e) => e.internalId == tag.internalId,
|
||||||
|
orElse: () => tag,
|
||||||
|
);
|
||||||
|
final isUpdated = matchingTag != tag;
|
||||||
|
return isUpdated
|
||||||
|
? tag.copyWith(tag: matchingTag?.tag)
|
||||||
|
: tag;
|
||||||
|
}) ??
|
||||||
|
<TagModel>[],
|
||||||
|
...?matchingEventSubspace.tags?.where(
|
||||||
|
(e) =>
|
||||||
|
subspace.tags
|
||||||
|
?.every((t) => t.internalId != e.internalId) ??
|
||||||
|
true,
|
||||||
|
) ??
|
||||||
|
<TagModel>[],
|
||||||
|
];
|
||||||
|
return subspace.copyWith(
|
||||||
|
subspaceName: matchingEventSubspace.subspaceName,
|
||||||
|
tags: updatedTags,
|
||||||
|
);
|
||||||
|
}).toList() ??
|
||||||
|
[];
|
||||||
|
|
||||||
|
// Add new subspaces
|
||||||
|
event.subspaces
|
||||||
|
.where((e) =>
|
||||||
|
updatedSubspaces.every((s) => s.internalId != e.internalId))
|
||||||
|
.forEach((newSubspace) {
|
||||||
|
updatedSubspaces.add(newSubspace);
|
||||||
|
});
|
||||||
|
|
||||||
|
final updatedSpace =
|
||||||
|
currentState.space.copyWith(subspaceModels: updatedSubspaces);
|
||||||
|
|
||||||
emit(CreateSpaceModelLoaded(updatedSpace));
|
emit(CreateSpaceModelLoaded(updatedSpace));
|
||||||
} else {
|
} else {
|
||||||
emit(CreateSpaceModelError("Space template not initialized"));
|
emit(CreateSpaceModelError("Space template not initialized"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
on<AddTagsToSpaceTemplate>((event, emit) {
|
||||||
|
final currentState = state;
|
||||||
|
|
||||||
|
if (currentState is CreateSpaceModelLoaded) {
|
||||||
|
final eventTagIds = event.tags.map((e) => e.internalId).toSet();
|
||||||
|
|
||||||
|
final updatedTags = currentState.space.tags
|
||||||
|
?.where((tag) => eventTagIds.contains(tag.internalId))
|
||||||
|
.map((tag) {
|
||||||
|
final matchingEventTag = event.tags.firstWhere(
|
||||||
|
(e) => e.internalId == tag.internalId,
|
||||||
|
orElse: () => tag,
|
||||||
|
);
|
||||||
|
return matchingEventTag != tag
|
||||||
|
? tag.copyWith(tag: matchingEventTag.tag)
|
||||||
|
: tag;
|
||||||
|
}).toList() ??
|
||||||
|
[];
|
||||||
|
|
||||||
|
event.tags
|
||||||
|
.where(
|
||||||
|
(e) => updatedTags.every((t) => t.internalId != e.internalId))
|
||||||
|
.forEach((e) {
|
||||||
|
updatedTags.add(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
emit(CreateSpaceModelLoaded(
|
||||||
|
currentState.space.copyWith(tags: updatedTags)));
|
||||||
|
} else {
|
||||||
|
emit(CreateSpaceModelError("Space template not initialized"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
on<UpdateSpaceTemplateName>((event, emit) {
|
on<UpdateSpaceTemplateName>((event, emit) {
|
||||||
final currentState = state;
|
final currentState = state;
|
||||||
if (currentState is CreateSpaceModelLoaded) {
|
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(
|
emit(CreateSpaceModelLoaded(
|
||||||
currentState.space,
|
currentState.space,
|
||||||
errorMessage: "Model name cannot be empty",
|
errorMessage: "Model name cannot be empty",
|
||||||
@ -103,5 +194,160 @@ class CreateSpaceModelBloc
|
|||||||
emit(CreateSpaceModelError("Space template not initialized"));
|
emit(CreateSpaceModelError("Space template not initialized"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
on<ModifySpaceTemplate>((event, emit) async {
|
||||||
|
try {
|
||||||
|
final prevSpaceModel = event.spaceTemplate;
|
||||||
|
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((newTag) => newTag.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,
|
||||||
|
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));
|
||||||
|
} else {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (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) {
|
||||||
|
for (var newTag in newTags!) {
|
||||||
|
// Tag without UUID
|
||||||
|
if ((newTag.uuid == null || newTag.uuid!.isEmpty) &&
|
||||||
|
!processedTags.contains(newTag.tag)) {
|
||||||
|
tagUpdates.add(TagModelUpdate(
|
||||||
|
action: Action.add,
|
||||||
|
tag: newTag.tag,
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
||||||
|
|
||||||
abstract class CreateSpaceModelEvent extends Equatable {
|
abstract class CreateSpaceModelEvent extends Equatable {
|
||||||
const CreateSpaceModelEvent();
|
const CreateSpaceModelEvent();
|
||||||
@ -32,11 +33,12 @@ class CreateSpaceTemplate extends CreateSpaceModelEvent {
|
|||||||
|
|
||||||
class UpdateSpaceTemplateName extends CreateSpaceModelEvent {
|
class UpdateSpaceTemplateName extends CreateSpaceModelEvent {
|
||||||
final String name;
|
final String name;
|
||||||
|
final List<String> allModels;
|
||||||
|
|
||||||
UpdateSpaceTemplateName({required this.name});
|
UpdateSpaceTemplateName({required this.name, required this.allModels});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [name];
|
List<Object> get props => [name, allModels];
|
||||||
}
|
}
|
||||||
|
|
||||||
class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent {
|
class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent {
|
||||||
@ -45,8 +47,25 @@ class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent {
|
|||||||
AddSubspacesToSpaceTemplate(this.subspaces);
|
AddSubspacesToSpaceTemplate(this.subspaces);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AddTagsToSpaceTemplate extends CreateSpaceModelEvent {
|
||||||
|
final List<TagModel> tags;
|
||||||
|
|
||||||
|
AddTagsToSpaceTemplate(this.tags);
|
||||||
|
}
|
||||||
|
|
||||||
class ValidateSpaceTemplateName extends CreateSpaceModelEvent {
|
class ValidateSpaceTemplateName extends CreateSpaceModelEvent {
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
ValidateSpaceTemplateName({required this.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,
|
required List<SpaceTemplateModel> initialSpaceModels,
|
||||||
}) : super(SpaceModelLoaded(spaceModels: initialSpaceModels)) {
|
}) : super(SpaceModelLoaded(spaceModels: initialSpaceModels)) {
|
||||||
on<CreateSpaceModel>(_onCreateSpaceModel);
|
on<CreateSpaceModel>(_onCreateSpaceModel);
|
||||||
|
on<UpdateSpaceModel>(_onUpdateSpaceModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onCreateSpaceModel(
|
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
|
@override
|
||||||
List<Object?> get props => [newSpaceModel];
|
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];
|
||||||
|
}
|
||||||
|
|||||||
@ -30,12 +30,12 @@ class CreateSubspaceTemplateModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CreateSpaceTemplateBodyModel {
|
class CreateSpaceTemplateBodyModel {
|
||||||
final String modelName;
|
final String? modelName;
|
||||||
final List<dynamic>? tags;
|
final List<dynamic>? tags;
|
||||||
final List<dynamic>? subspaceModels;
|
final List<dynamic>? subspaceModels;
|
||||||
|
|
||||||
CreateSpaceTemplateBodyModel({
|
CreateSpaceTemplateBodyModel({
|
||||||
required this.modelName,
|
this.modelName,
|
||||||
this.tags,
|
this.tags,
|
||||||
this.subspaceModels,
|
this.subspaceModels,
|
||||||
});
|
});
|
||||||
@ -47,4 +47,9 @@ class CreateSpaceTemplateBodyModel {
|
|||||||
'subspaceModels': subspaceModels,
|
'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/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/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_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:syncrow_web/utils/constants/action_enum.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
@ -13,7 +14,7 @@ class SpaceTemplateModel extends Equatable {
|
|||||||
String internalId;
|
String internalId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [modelName, subspaceModels];
|
List<Object?> get props => [modelName, subspaceModels, tags];
|
||||||
|
|
||||||
SpaceTemplateModel({
|
SpaceTemplateModel({
|
||||||
this.uuid,
|
this.uuid,
|
||||||
@ -70,14 +71,14 @@ class SpaceTemplateModel extends Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class UpdateSubspaceTemplateModel {
|
class UpdateSubspaceTemplateModel {
|
||||||
final String uuid;
|
final String? uuid;
|
||||||
final Action action;
|
final Action action;
|
||||||
final String? subspaceName;
|
final String? subspaceName;
|
||||||
final List<UpdateTagModel>? tags;
|
final List<TagModelUpdate>? tags;
|
||||||
|
|
||||||
UpdateSubspaceTemplateModel({
|
UpdateSubspaceTemplateModel({
|
||||||
required this.action,
|
required this.action,
|
||||||
required this.uuid,
|
this.uuid,
|
||||||
this.subspaceName,
|
this.subspaceName,
|
||||||
this.tags,
|
this.tags,
|
||||||
});
|
});
|
||||||
@ -88,7 +89,7 @@ class UpdateSubspaceTemplateModel {
|
|||||||
uuid: json['uuid'] ?? '',
|
uuid: json['uuid'] ?? '',
|
||||||
subspaceName: json['subspaceName'] ?? '',
|
subspaceName: json['subspaceName'] ?? '',
|
||||||
tags: (json['tags'] as List)
|
tags: (json['tags'] as List)
|
||||||
.map((item) => UpdateTagModel.fromJson(item))
|
.map((item) => TagModelUpdate.fromJson(item))
|
||||||
.toList(),
|
.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 {
|
extension SpaceTemplateExtensions on SpaceTemplateModel {
|
||||||
List<String> listAllTagValues() {
|
List<String> listAllTagValues() {
|
||||||
final List<String> tagValues = [];
|
final List<String> tagValues = [];
|
||||||
|
|||||||
@ -1,22 +1,28 @@
|
|||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class SubspaceTemplateModel {
|
class SubspaceTemplateModel {
|
||||||
final String? uuid;
|
final String? uuid;
|
||||||
String subspaceName;
|
String subspaceName;
|
||||||
final bool disabled;
|
final bool disabled;
|
||||||
List<TagModel>? tags;
|
List<TagModel>? tags;
|
||||||
|
String internalId;
|
||||||
|
|
||||||
SubspaceTemplateModel({
|
SubspaceTemplateModel({
|
||||||
this.uuid,
|
this.uuid,
|
||||||
required this.subspaceName,
|
required this.subspaceName,
|
||||||
required this.disabled,
|
required this.disabled,
|
||||||
this.tags,
|
this.tags,
|
||||||
});
|
String? internalId,
|
||||||
|
}) : internalId = internalId ?? const Uuid().v4();
|
||||||
|
|
||||||
factory SubspaceTemplateModel.fromJson(Map<String, dynamic> json) {
|
factory SubspaceTemplateModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
final String internalId = json['internalId'] ?? const Uuid().v4();
|
||||||
|
|
||||||
return SubspaceTemplateModel(
|
return SubspaceTemplateModel(
|
||||||
uuid: json['uuid'] ?? '',
|
uuid: json['uuid'] ?? '',
|
||||||
subspaceName: json['subspaceName'] ?? '',
|
subspaceName: json['subspaceName'] ?? '',
|
||||||
|
internalId: internalId,
|
||||||
disabled: json['disabled'] ?? false,
|
disabled: json['disabled'] ?? false,
|
||||||
tags: (json['tags'] as List<dynamic>?)
|
tags: (json['tags'] as List<dynamic>?)
|
||||||
?.map((item) => TagModel.fromJson(item))
|
?.map((item) => TagModel.fromJson(item))
|
||||||
@ -33,4 +39,20 @@ class SubspaceTemplateModel {
|
|||||||
'tags': tags?.map((e) => e.toJson()).toList() ?? [],
|
'tags': tags?.map((e) => e.toJson()).toList() ?? [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SubspaceTemplateModel copyWith({
|
||||||
|
String? uuid,
|
||||||
|
String? subspaceName,
|
||||||
|
bool? disabled,
|
||||||
|
List<TagModel>? tags,
|
||||||
|
String? internalId,
|
||||||
|
}) {
|
||||||
|
return SubspaceTemplateModel(
|
||||||
|
uuid: uuid ?? this.uuid,
|
||||||
|
subspaceName: subspaceName ?? this.subspaceName,
|
||||||
|
disabled: disabled ?? this.disabled,
|
||||||
|
tags: tags ?? this.tags,
|
||||||
|
internalId: internalId ?? this.internalId,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,16 @@
|
|||||||
|
class CreateTagBodyModel {
|
||||||
|
late String tag;
|
||||||
|
late final String? productUuid;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'tag': tag,
|
||||||
|
'productUuid': productUuid,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return toJson().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -30,15 +30,16 @@ class TagModel {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TagModel copyWith({
|
TagModel copyWith(
|
||||||
String? tag,
|
{String? tag,
|
||||||
ProductModel? product,
|
ProductModel? product,
|
||||||
String? location,
|
String? location,
|
||||||
}) {
|
String? internalId}) {
|
||||||
return TagModel(
|
return TagModel(
|
||||||
tag: tag ?? this.tag,
|
tag: tag ?? this.tag,
|
||||||
product: product ?? this.product,
|
product: product ?? this.product,
|
||||||
location: location ?? this.location,
|
location: location ?? this.location,
|
||||||
|
internalId: internalId ?? this.internalId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/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_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/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/models/space_template_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/add_space_model_widget.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/add_space_model_widget.dart';
|
||||||
@ -24,6 +23,7 @@ class SpaceModelPage extends StatelessWidget {
|
|||||||
} else if (state is SpaceModelLoaded) {
|
} else if (state is SpaceModelLoaded) {
|
||||||
final spaceModels = state.spaceModels;
|
final spaceModels = state.spaceModels;
|
||||||
final allTagValues = _getAllTagValues(spaceModels);
|
final allTagValues = _getAllTagValues(spaceModels);
|
||||||
|
final allSpaceModelNames = _getAllSpaceModelName(spaceModels);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: ColorsManager.whiteColors,
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
@ -52,12 +52,8 @@ class SpaceModelPage extends StatelessWidget {
|
|||||||
return CreateSpaceModelDialog(
|
return CreateSpaceModelDialog(
|
||||||
products: products,
|
products: products,
|
||||||
allTags: allTagValues,
|
allTags: allTagValues,
|
||||||
onLoad: (newModel) {
|
pageContext: context,
|
||||||
context.read<SpaceModelBloc>().add(
|
otherSpaceModels: allSpaceModelNames,
|
||||||
CreateSpaceModel(
|
|
||||||
newSpaceModel: newModel),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -67,10 +63,27 @@ class SpaceModelPage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
// Render existing space model
|
// Render existing space model
|
||||||
final model = spaceModels[index];
|
final model = spaceModels[index];
|
||||||
return Container(
|
final otherModel = List<String>.from(allSpaceModelNames);
|
||||||
|
otherModel.remove(model.modelName);
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext dialogContext) {
|
||||||
|
return CreateSpaceModelDialog(
|
||||||
|
products: products,
|
||||||
|
allTags: allTagValues,
|
||||||
|
spaceModel: model,
|
||||||
|
otherSpaceModels: otherModel,
|
||||||
|
pageContext: context,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
margin: const EdgeInsets.all(8.0),
|
margin: const EdgeInsets.all(8.0),
|
||||||
child: SpaceModelCardWidget(model: model),
|
child: SpaceModelCardWidget(model: model),
|
||||||
);
|
));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -94,14 +107,14 @@ class SpaceModelPage extends StatelessWidget {
|
|||||||
double _calculateChildAspectRatio(BuildContext context) {
|
double _calculateChildAspectRatio(BuildContext context) {
|
||||||
double screenWidth = MediaQuery.of(context).size.width;
|
double screenWidth = MediaQuery.of(context).size.width;
|
||||||
if (screenWidth > 1600) {
|
if (screenWidth > 1600) {
|
||||||
return 2; // Taller cards for larger screens
|
return 2;
|
||||||
}
|
}
|
||||||
if (screenWidth > 1200) {
|
if (screenWidth > 1200) {
|
||||||
return 3; // Adjusted height for medium screens
|
return 3;
|
||||||
} else if (screenWidth > 800) {
|
} else if (screenWidth > 800) {
|
||||||
return 3.5; // Adjusted height for smaller screens
|
return 3.5;
|
||||||
} else {
|
} else {
|
||||||
return 4.0; // Default ratio for smallest screens
|
return 4.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,4 +127,12 @@ class SpaceModelPage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
return allTags;
|
return allTags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<String> _getAllSpaceModelName(List<SpaceTemplateModel> spaceModels) {
|
||||||
|
final List<String> names = [];
|
||||||
|
for (final spaceModel in spaceModels) {
|
||||||
|
names.add(spaceModel.modelName);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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_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_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/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/models/space_template_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/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/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart';
|
||||||
import 'package:syncrow_web/services/space_model_mang_api.dart';
|
import 'package:syncrow_web/services/space_model_mang_api.dart';
|
||||||
@ -17,15 +20,17 @@ class CreateSpaceModelDialog extends StatelessWidget {
|
|||||||
final List<ProductModel>? products;
|
final List<ProductModel>? products;
|
||||||
final List<String>? allTags;
|
final List<String>? allTags;
|
||||||
final SpaceTemplateModel? spaceModel;
|
final SpaceTemplateModel? spaceModel;
|
||||||
final void Function(SpaceTemplateModel newModel)? onLoad;
|
final BuildContext? pageContext;
|
||||||
|
final List<String>? otherSpaceModels;
|
||||||
|
|
||||||
const CreateSpaceModelDialog({
|
const CreateSpaceModelDialog(
|
||||||
Key? key,
|
{Key? key,
|
||||||
this.products,
|
this.products,
|
||||||
this.allTags,
|
this.allTags,
|
||||||
this.spaceModel,
|
this.spaceModel,
|
||||||
this.onLoad,
|
this.pageContext,
|
||||||
}) : super(key: key);
|
this.otherSpaceModels})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -54,7 +59,9 @@ class CreateSpaceModelDialog extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
spaceNameController.addListener(() {
|
spaceNameController.addListener(() {
|
||||||
bloc.add(UpdateSpaceTemplateName(name: spaceNameController.text));
|
bloc.add(UpdateSpaceTemplateName(
|
||||||
|
name: spaceNameController.text,
|
||||||
|
allModels: otherSpaceModels ?? []));
|
||||||
});
|
});
|
||||||
|
|
||||||
return bloc;
|
return bloc;
|
||||||
@ -72,7 +79,9 @@ class CreateSpaceModelDialog extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Create New Space Model',
|
spaceModel?.uuid == null
|
||||||
|
? 'Create New Space Model'
|
||||||
|
: 'Edit Space Model',
|
||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.headlineLarge
|
.headlineLarge
|
||||||
@ -84,9 +93,10 @@ class CreateSpaceModelDialog extends StatelessWidget {
|
|||||||
child: TextField(
|
child: TextField(
|
||||||
controller: spaceNameController,
|
controller: spaceNameController,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
context
|
context.read<CreateSpaceModelBloc>().add(
|
||||||
.read<CreateSpaceModelBloc>()
|
UpdateSpaceTemplateName(
|
||||||
.add(UpdateSpaceTemplateName(name: value));
|
name: value,
|
||||||
|
allModels: otherSpaceModels ?? []));
|
||||||
},
|
},
|
||||||
style: const TextStyle(color: ColorsManager.blackColor),
|
style: const TextStyle(color: ColorsManager.blackColor),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@ -108,21 +118,27 @@ class CreateSpaceModelDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
SubspaceModelCreate(context,
|
SubspaceModelCreate(
|
||||||
|
context,
|
||||||
subspaces: state.space.subspaceModels ?? [],
|
subspaces: state.space.subspaceModels ?? [],
|
||||||
allTags: allTags,
|
onSpaceModelUpdate: (updatedSubspaces) {
|
||||||
products: products,
|
context
|
||||||
spaceModel: spaceModel,
|
.read<CreateSpaceModelBloc>()
|
||||||
spaceTagModels: spaceModel?.tags ?? [],
|
.add(AddSubspacesToSpaceTemplate(updatedSubspaces));
|
||||||
spaceNameController: spaceNameController),
|
},
|
||||||
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
TagChipDisplay(context,
|
TagChipDisplay(
|
||||||
|
context,
|
||||||
screenWidth: screenWidth,
|
screenWidth: screenWidth,
|
||||||
spaceModel: updatedSpaceModel,
|
spaceModel: updatedSpaceModel,
|
||||||
products: products,
|
products: products,
|
||||||
subspaces: subspaces,
|
subspaces: subspaces,
|
||||||
allTags: allTags,
|
allTags: allTags,
|
||||||
spaceNameController: spaceNameController),
|
spaceNameController: spaceNameController,
|
||||||
|
pageContext: pageContext,
|
||||||
|
otherSpaceModels: otherSpaceModels,
|
||||||
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: screenWidth * 0.25,
|
width: screenWidth * 0.25,
|
||||||
@ -145,17 +161,73 @@ class CreateSpaceModelDialog extends StatelessWidget {
|
|||||||
modelName:
|
modelName:
|
||||||
spaceNameController.text.trim(),
|
spaceNameController.text.trim(),
|
||||||
);
|
);
|
||||||
context.read<CreateSpaceModelBloc>().add(
|
if (updatedSpaceModel.uuid == null) {
|
||||||
|
context
|
||||||
|
.read<CreateSpaceModelBloc>()
|
||||||
|
.add(
|
||||||
CreateSpaceTemplate(
|
CreateSpaceTemplate(
|
||||||
spaceTemplate:
|
spaceTemplate:
|
||||||
updatedSpaceTemplate,
|
updatedSpaceTemplate,
|
||||||
onCreate: (newModel) {
|
onCreate: (newModel) {
|
||||||
onLoad!(newModel);
|
if (pageContext != null) {
|
||||||
|
pageContext!
|
||||||
|
.read<SpaceModelBloc>()
|
||||||
|
.add(CreateSpaceModel(
|
||||||
|
newSpaceModel:
|
||||||
|
newModel));
|
||||||
|
}
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
.pop(); // Close the dialog
|
.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,
|
: null,
|
||||||
backgroundColor: ColorsManager.secondaryColor,
|
backgroundColor: ColorsManager.secondaryColor,
|
||||||
|
|||||||
@ -0,0 +1,66 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/ellipsis_item_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/flexible_item_widget.dart';
|
||||||
|
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class DynamicProductWidget extends StatelessWidget {
|
||||||
|
final Map<String, int> productTagCount;
|
||||||
|
final double maxWidth;
|
||||||
|
final double maxHeight;
|
||||||
|
|
||||||
|
const DynamicProductWidget({
|
||||||
|
Key? key,
|
||||||
|
required this.productTagCount,
|
||||||
|
required this.maxWidth,
|
||||||
|
required this.maxHeight,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
const double itemSpacing = 8.0;
|
||||||
|
const double lineSpacing = 8.0;
|
||||||
|
const double textPadding = 16.0;
|
||||||
|
const double itemHeight = 40.0;
|
||||||
|
|
||||||
|
List<Widget> productWidgets = [];
|
||||||
|
double currentLineWidth = 0.0;
|
||||||
|
double currentHeight = itemHeight;
|
||||||
|
|
||||||
|
for (final product in productTagCount.entries) {
|
||||||
|
final String prodType = product.key;
|
||||||
|
final int count = product.value;
|
||||||
|
|
||||||
|
final TextPainter textPainter = TextPainter(
|
||||||
|
text: TextSpan(
|
||||||
|
text: 'x$count',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall
|
||||||
|
?.copyWith(color: ColorsManager.spaceColor),
|
||||||
|
),
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
)..layout();
|
||||||
|
|
||||||
|
final double itemWidth = textPainter.width + textPadding + 20;
|
||||||
|
|
||||||
|
if (currentLineWidth + itemWidth + itemSpacing > maxWidth) {
|
||||||
|
currentHeight += itemHeight + lineSpacing;
|
||||||
|
if (currentHeight > maxHeight) {
|
||||||
|
productWidgets.add(const EllipsisItemWidget());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
currentLineWidth = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
productWidgets.add(FlexibleItemWidget(prodType: prodType, count: count));
|
||||||
|
currentLineWidth += itemWidth + itemSpacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Wrap(
|
||||||
|
spacing: itemSpacing,
|
||||||
|
runSpacing: lineSpacing,
|
||||||
|
children: productWidgets,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
import 'package:flutter/material.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/room_name_widget.dart';
|
||||||
|
|
||||||
|
class DynamicRoomWidget extends StatelessWidget {
|
||||||
|
final List<SubspaceTemplateModel>? subspaceModels;
|
||||||
|
final double maxWidth;
|
||||||
|
final double maxHeight;
|
||||||
|
|
||||||
|
const DynamicRoomWidget({
|
||||||
|
Key? key,
|
||||||
|
required this.subspaceModels,
|
||||||
|
required this.maxWidth,
|
||||||
|
required this.maxHeight,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
const double itemSpacing = 8.0;
|
||||||
|
const double lineSpacing = 8.0;
|
||||||
|
const double textPadding = 16.0;
|
||||||
|
const double itemHeight = 30.0;
|
||||||
|
|
||||||
|
List<Widget> roomWidgets = [];
|
||||||
|
double currentLineWidth = 0.0;
|
||||||
|
double currentHeight = itemHeight;
|
||||||
|
|
||||||
|
for (final subspace in subspaceModels!) {
|
||||||
|
final TextPainter textPainter = TextPainter(
|
||||||
|
text: TextSpan(
|
||||||
|
text: subspace.subspaceName,
|
||||||
|
style: const TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
)..layout();
|
||||||
|
|
||||||
|
final double itemWidth = textPainter.width + textPadding;
|
||||||
|
|
||||||
|
if (currentLineWidth + itemWidth + itemSpacing > maxWidth) {
|
||||||
|
currentHeight += itemHeight + lineSpacing;
|
||||||
|
if (currentHeight > maxHeight) {
|
||||||
|
roomWidgets.add(const RoomNameWidget(name: "..."));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
currentLineWidth = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
roomWidgets.add(RoomNameWidget(name: subspace.subspaceName));
|
||||||
|
currentLineWidth += itemWidth + itemSpacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Wrap(
|
||||||
|
spacing: itemSpacing,
|
||||||
|
runSpacing: lineSpacing,
|
||||||
|
children: roomWidgets,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class EllipsisItemWidget extends StatelessWidget {
|
||||||
|
const EllipsisItemWidget({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.textFieldGreyColor,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: ColorsManager.transparentColor),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
"...",
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall
|
||||||
|
?.copyWith(color: ColorsManager.spaceColor),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class FlexibleItemWidget extends StatelessWidget {
|
||||||
|
final String prodType;
|
||||||
|
final int count;
|
||||||
|
|
||||||
|
const FlexibleItemWidget({
|
||||||
|
Key? key,
|
||||||
|
required this.prodType,
|
||||||
|
required this.count,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.textFieldGreyColor,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: ColorsManager.transparentColor),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SvgPicture.asset(
|
||||||
|
prodType,
|
||||||
|
width: 15,
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(
|
||||||
|
'x$count',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall
|
||||||
|
?.copyWith(color: ColorsManager.spaceColor),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class RoomNameWidget extends StatelessWidget {
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
const RoomNameWidget({Key? key, required this.name}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.textFieldGreyColor,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: ColorsManager.transparentColor),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
name,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall
|
||||||
|
?.copyWith(color: ColorsManager.spaceColor),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_chip_widget.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_product_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class SpaceModelCardWidget extends StatelessWidget {
|
class SpaceModelCardWidget extends StatelessWidget {
|
||||||
@ -32,111 +32,81 @@ class SpaceModelCardWidget extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: ColorsManager.whiteColors,
|
color: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: ColorsManager.lightGrayColor.withOpacity(0.5),
|
color: Colors.grey.withOpacity(0.5),
|
||||||
spreadRadius: 2,
|
spreadRadius: 2,
|
||||||
blurRadius: 5,
|
blurRadius: 5,
|
||||||
offset: const Offset(0, 3),
|
offset: const Offset(0, 3),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.fromLTRB(16.0, 14.0, 8.0, 8.0),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Text(
|
||||||
child: Text(
|
|
||||||
model.modelName,
|
model.modelName,
|
||||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||||
color: ColorsManager.blackColor,
|
color: Colors.black,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
|
// Left Container
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Wrap(
|
flex: 1, // Distribute space proportionally
|
||||||
spacing: 3.0,
|
child: Container(
|
||||||
runSpacing: 3.0,
|
padding: const EdgeInsets.all(8.0),
|
||||||
children: [
|
child: LayoutBuilder(
|
||||||
for (var subspace in model.subspaceModels!
|
builder: (context, constraints) {
|
||||||
.take(calculateTakeCount(context)))
|
return Align(
|
||||||
SubspaceChipWidget(subspace: subspace.subspaceName),
|
alignment: Alignment.topLeft,
|
||||||
],
|
child: DynamicRoomWidget(
|
||||||
|
subspaceModels: model.subspaceModels,
|
||||||
|
maxWidth: constraints.maxWidth,
|
||||||
|
maxHeight: constraints.maxHeight,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (productTagCount.isNotEmpty)
|
),
|
||||||
|
if (productTagCount.isNotEmpty && model.subspaceModels != null)
|
||||||
Container(
|
Container(
|
||||||
width: 1,
|
width: 1.0,
|
||||||
height: double.infinity,
|
|
||||||
color: ColorsManager.softGray,
|
color: ColorsManager.softGray,
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 4.0),
|
margin: const EdgeInsets.symmetric(vertical: 6.0),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 7),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Wrap(
|
flex: 1, // Distribute space proportionally
|
||||||
spacing: 4.0,
|
child: Container(
|
||||||
runSpacing: 4.0,
|
padding: const EdgeInsets.all(8.0),
|
||||||
children: productTagCount.entries.map((entry) {
|
child: LayoutBuilder(
|
||||||
final prodType = entry.key;
|
builder: (context, constraints) {
|
||||||
final count = entry.value;
|
return Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
return Chip(
|
child: DynamicProductWidget(
|
||||||
label: Row(
|
productTagCount: productTagCount,
|
||||||
mainAxisSize: MainAxisSize.min,
|
maxWidth: constraints.maxWidth,
|
||||||
children: [
|
maxHeight: constraints.maxHeight));
|
||||||
SvgPicture.asset(
|
},
|
||||||
prodType,
|
|
||||||
width: 15,
|
|
||||||
height: 16,
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
|
||||||
Text(
|
|
||||||
'x$count', // Product count
|
|
||||||
style: const TextStyle(fontSize: 12),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
backgroundColor: ColorsManager.textFieldGreyColor,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
side: const BorderSide(
|
|
||||||
color: ColorsManager.transparentColor,
|
|
||||||
width: 0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 5),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
int calculateTakeCount(BuildContext context) {
|
|
||||||
double screenWidth = MediaQuery.of(context).size.width;
|
|
||||||
// Adjust the count based on the screen width
|
|
||||||
if (screenWidth > 1500) {
|
|
||||||
return 3; // For large screens
|
|
||||||
} else if (screenWidth > 1200) {
|
|
||||||
return 2;
|
|
||||||
} else {
|
|
||||||
return 1; // For smaller screens
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,24 +11,25 @@ class SubspaceChipWidget extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Chip(
|
return Container(
|
||||||
label: Text(
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
subspace,
|
decoration: BoxDecoration(
|
||||||
overflow: TextOverflow.ellipsis,
|
color: ColorsManager.textFieldGreyColor,
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
backgroundColor: ColorsManager.textFieldGreyColor,
|
|
||||||
labelStyle: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodySmall
|
|
||||||
?.copyWith(color: ColorsManager.spaceColor),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
side: const BorderSide(
|
border: Border.all(
|
||||||
color: ColorsManager.transparentColor,
|
color: ColorsManager.transparentColor,
|
||||||
width: 0,
|
width: 0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
child: Text(
|
||||||
|
subspace,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall
|
||||||
|
?.copyWith(color: ColorsManager.spaceColor),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,30 +1,17 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/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/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/create_subspace_model/views/create_subspace_model_dialog.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class SubspaceModelCreate extends StatelessWidget {
|
class SubspaceModelCreate extends StatelessWidget {
|
||||||
final List<SubspaceTemplateModel> subspaces;
|
final List<SubspaceTemplateModel> subspaces;
|
||||||
final TextEditingController spaceNameController;
|
final void Function(List<SubspaceTemplateModel> newSubspaces)?
|
||||||
final List<TagModel>? spaceTagModels;
|
onSpaceModelUpdate;
|
||||||
final List<String>? allTags;
|
|
||||||
final List<ProductModel>? products;
|
|
||||||
final SpaceTemplateModel? spaceModel;
|
|
||||||
|
|
||||||
const SubspaceModelCreate(
|
const SubspaceModelCreate(BuildContext context,
|
||||||
BuildContext context, {
|
{Key? key, required this.subspaces, this.onSpaceModelUpdate})
|
||||||
Key? key,
|
: super(key: key);
|
||||||
required this.subspaces,
|
|
||||||
this.spaceTagModels,
|
|
||||||
required this.allTags,
|
|
||||||
required this.products,
|
|
||||||
required this.spaceModel,
|
|
||||||
required this.spaceNameController,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -36,26 +23,7 @@ class SubspaceModelCreate extends StatelessWidget {
|
|||||||
overlayColor: ColorsManager.transparentColor,
|
overlayColor: ColorsManager.transparentColor,
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.of(context).pop();
|
await _openDialog(context, 'Create Sub-space');
|
||||||
|
|
||||||
await showDialog<List<SubspaceTemplateModel>>(
|
|
||||||
barrierDismissible: false,
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return CreateSubSpaceModelDialog(
|
|
||||||
allTags: allTags,
|
|
||||||
spaceName: spaceNameController.text,
|
|
||||||
spaceModel: spaceModel,
|
|
||||||
spaceTagModels: spaceTagModels,
|
|
||||||
products: products,
|
|
||||||
isEdit: true,
|
|
||||||
dialogTitle: subspaces.isEmpty
|
|
||||||
? 'Create Sub-space'
|
|
||||||
: 'Edit Sub-space',
|
|
||||||
existingSubSpaces: subspaces,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: const ButtonContentWidget(
|
child: const ButtonContentWidget(
|
||||||
icon: Icons.add,
|
icon: Icons.add,
|
||||||
@ -78,55 +46,37 @@ class SubspaceModelCreate extends StatelessWidget {
|
|||||||
spacing: 8.0,
|
spacing: 8.0,
|
||||||
runSpacing: 8.0,
|
runSpacing: 8.0,
|
||||||
children: [
|
children: [
|
||||||
...subspaces.map(
|
...subspaces.map((subspace) => Container(
|
||||||
(subspace) => Chip(
|
padding: const EdgeInsets.symmetric(
|
||||||
label: Text(
|
horizontal: 8.0, vertical: 4.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorsManager.transparentColor),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
subspace.subspaceName,
|
subspace.subspaceName,
|
||||||
style: const TextStyle(
|
style: Theme.of(context)
|
||||||
color: ColorsManager.spaceColor), // Text color
|
.textTheme
|
||||||
),
|
.bodySmall
|
||||||
backgroundColor: ColorsManager.whiteColors, // Chip background color
|
?.copyWith(color: ColorsManager.spaceColor),
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.circular(16), // Rounded chip
|
|
||||||
side: const BorderSide(
|
|
||||||
color: ColorsManager.spaceColor), // Border color
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
)),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
Navigator.of(context).pop();
|
await _openDialog(context, 'Edit Sub-space');
|
||||||
await showDialog<List<SubspaceTemplateModel>>(
|
|
||||||
barrierDismissible: false,
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return CreateSubSpaceModelDialog(
|
|
||||||
isEdit: true,
|
|
||||||
dialogTitle: 'Edit Sub-space',
|
|
||||||
existingSubSpaces: subspaces,
|
|
||||||
allTags: allTags,
|
|
||||||
spaceName: spaceNameController.text,
|
|
||||||
spaceTagModels: spaceTagModels,
|
|
||||||
products: products,
|
|
||||||
spaceModel: spaceModel,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: Chip(
|
child: Chip(
|
||||||
label: const Text(
|
label: const Text(
|
||||||
'Edit',
|
'Edit',
|
||||||
style: TextStyle(
|
style: TextStyle(color: ColorsManager.spaceColor),
|
||||||
color: ColorsManager.spaceColor),
|
|
||||||
),
|
),
|
||||||
backgroundColor:
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
ColorsManager.whiteColors,
|
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius:
|
borderRadius: BorderRadius.circular(16),
|
||||||
BorderRadius.circular(16),
|
side:
|
||||||
side: const BorderSide(
|
const BorderSide(color: ColorsManager.spaceColor),
|
||||||
color: ColorsManager.spaceColor),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -136,4 +86,21 @@ class SubspaceModelCreate extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _openDialog(BuildContext context, String dialogTitle) async {
|
||||||
|
await showDialog(
|
||||||
|
barrierDismissible: false,
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return CreateSubSpaceModelDialog(
|
||||||
|
isEdit: true,
|
||||||
|
dialogTitle: dialogTitle,
|
||||||
|
existingSubSpaces: subspaces,
|
||||||
|
onUpdate: (subspaceModels) {
|
||||||
|
onSpaceModelUpdate!(subspaceModels);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/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/space_template_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/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/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/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
@ -16,17 +16,20 @@ class TagChipDisplay extends StatelessWidget {
|
|||||||
final List<SubspaceTemplateModel>? subspaces;
|
final List<SubspaceTemplateModel>? subspaces;
|
||||||
final List<String>? allTags;
|
final List<String>? allTags;
|
||||||
final TextEditingController spaceNameController;
|
final TextEditingController spaceNameController;
|
||||||
|
final BuildContext? pageContext;
|
||||||
|
final List<String>? otherSpaceModels;
|
||||||
|
|
||||||
const TagChipDisplay(
|
const TagChipDisplay(BuildContext context,
|
||||||
BuildContext context, {
|
{Key? key,
|
||||||
Key? key,
|
|
||||||
required this.screenWidth,
|
required this.screenWidth,
|
||||||
required this.spaceModel,
|
required this.spaceModel,
|
||||||
required this.products,
|
required this.products,
|
||||||
required this.subspaces,
|
required this.subspaces,
|
||||||
required this.allTags,
|
required this.allTags,
|
||||||
required this.spaceNameController,
|
required this.spaceNameController,
|
||||||
}) : super(key: key);
|
this.pageContext,
|
||||||
|
this.otherSpaceModels})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -51,7 +54,7 @@ class TagChipDisplay extends StatelessWidget {
|
|||||||
runSpacing: 8.0,
|
runSpacing: 8.0,
|
||||||
children: [
|
children: [
|
||||||
// Combine tags from spaceModel and subspaces
|
// Combine tags from spaceModel and subspaces
|
||||||
..._groupTags([
|
...TagHelper.groupTags([
|
||||||
...?spaceModel?.tags,
|
...?spaceModel?.tags,
|
||||||
...?spaceModel?.subspaceModels
|
...?spaceModel?.subspaceModels
|
||||||
?.expand((subspace) => subspace.tags ?? [])
|
?.expand((subspace) => subspace.tags ?? [])
|
||||||
@ -82,36 +85,40 @@ class TagChipDisplay extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
Navigator.of(context).pop();
|
// Use the Navigator's context for showDialog
|
||||||
|
final navigatorContext =
|
||||||
|
Navigator.of(context).overlay?.context;
|
||||||
|
|
||||||
|
if (navigatorContext != null) {
|
||||||
await showDialog<bool>(
|
await showDialog<bool>(
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
context: context,
|
context: navigatorContext,
|
||||||
builder: (context) => AddDeviceTypeModelWidget(
|
builder: (context) => AssignTagModelsDialog(
|
||||||
products: products,
|
products: products,
|
||||||
subspaces: subspaces,
|
subspaces: subspaces,
|
||||||
|
pageContext: pageContext,
|
||||||
allTags: allTags,
|
allTags: allTags,
|
||||||
spaceName: spaceNameController.text,
|
spaceModel: spaceModel,
|
||||||
spaceTagModels: spaceModel?.tags,
|
initialTags: TagHelper.generateInitialTags(
|
||||||
initialSelectedProducts:
|
subspaces: subspaces,
|
||||||
_createInitialSelectedProducts(
|
spaceTagModels: spaceModel?.tags ?? []),
|
||||||
spaceModel?.tags, spaceModel?.subspaceModels),
|
title: 'Edit Device',
|
||||||
),
|
addedProducts:
|
||||||
);
|
TagHelper.createInitialSelectedProducts(
|
||||||
// Edit action
|
spaceModel?.tags ?? [], subspaces),
|
||||||
|
spaceName: spaceModel?.modelName ?? '',
|
||||||
|
));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: Chip(
|
child: Chip(
|
||||||
label: const Text(
|
label: const Text(
|
||||||
'Edit',
|
'Edit',
|
||||||
style: TextStyle(
|
style: TextStyle(color: ColorsManager.spaceColor),
|
||||||
color: ColorsManager.spaceColor),
|
|
||||||
),
|
),
|
||||||
backgroundColor:
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
ColorsManager.whiteColors,
|
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
side: const BorderSide(
|
side: const BorderSide(color: ColorsManager.spaceColor),
|
||||||
color: ColorsManager.spaceColor),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -123,7 +130,7 @@ class TagChipDisplay extends StatelessWidget {
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
final result = await showDialog<bool>(
|
await showDialog<bool>(
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AddDeviceTypeModelWidget(
|
builder: (context) => AddDeviceTypeModelWidget(
|
||||||
@ -131,9 +138,11 @@ class TagChipDisplay extends StatelessWidget {
|
|||||||
subspaces: subspaces,
|
subspaces: subspaces,
|
||||||
allTags: allTags,
|
allTags: allTags,
|
||||||
spaceName: spaceNameController.text,
|
spaceName: spaceNameController.text,
|
||||||
|
pageContext: pageContext,
|
||||||
|
isCreate: true,
|
||||||
|
spaceModel: spaceModel,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (result == true) {}
|
|
||||||
},
|
},
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
@ -144,49 +153,4 @@ class TagChipDisplay extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<ProductModel, int> _groupTags(List<TagModel> tags) {
|
|
||||||
final Map<ProductModel, int> groupedTags = {};
|
|
||||||
for (var tag in tags) {
|
|
||||||
if (tag.product != null) {
|
|
||||||
groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return groupedTags;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<SelectedProduct> _createInitialSelectedProducts(
|
|
||||||
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces) {
|
|
||||||
final Map<ProductModel, int> productCounts = {};
|
|
||||||
|
|
||||||
if (tags != null) {
|
|
||||||
for (var tag in tags) {
|
|
||||||
if (tag.product != null) {
|
|
||||||
productCounts[tag.product!] = (productCounts[tag.product!] ?? 0) + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subspaces != null) {
|
|
||||||
for (var subspace in subspaces) {
|
|
||||||
if (subspace.tags != null) {
|
|
||||||
for (var tag in subspace.tags!) {
|
|
||||||
if (tag.product != null) {
|
|
||||||
productCounts[tag.product!] =
|
|
||||||
(productCounts[tag.product!] ?? 0) + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return productCounts.entries
|
|
||||||
.map((entry) => SelectedProduct(
|
|
||||||
productId: entry.key.uuid,
|
|
||||||
count: entry.value,
|
|
||||||
productName: entry.key.name ?? 'Unnamed',
|
|
||||||
product: entry.key,
|
|
||||||
))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,38 +1,75 @@
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.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';
|
import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart';
|
||||||
|
|
||||||
class AddDeviceTypeModelBloc
|
class AddDeviceTypeModelBloc
|
||||||
extends Bloc<AddDeviceTypeModelEvent, List<SelectedProduct>> {
|
extends Bloc<AddDeviceTypeModelEvent, AddDeviceModelState> {
|
||||||
AddDeviceTypeModelBloc(List<SelectedProduct> initialProducts)
|
AddDeviceTypeModelBloc() : super(AddDeviceModelInitial()) {
|
||||||
: super(initialProducts) {
|
on<InitializeDeviceTypeModel>(_onInitializeTagModels);
|
||||||
on<UpdateProductCountEvent>(_onUpdateProductCount);
|
on<UpdateProductCountEvent>(_onUpdateProductCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onInitializeTagModels(
|
||||||
|
InitializeDeviceTypeModel event, Emitter<AddDeviceModelState> emit) {
|
||||||
|
emit(AddDeviceModelLoaded(
|
||||||
|
selectedProducts: event.addedProducts,
|
||||||
|
initialTag: event.initialTags,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
void _onUpdateProductCount(
|
void _onUpdateProductCount(
|
||||||
UpdateProductCountEvent event, Emitter<List<SelectedProduct>> emit) {
|
UpdateProductCountEvent event, Emitter<AddDeviceModelState> emit) {
|
||||||
final existingProduct = state.firstWhere(
|
final currentState = state;
|
||||||
|
|
||||||
|
if (currentState is AddDeviceModelLoaded) {
|
||||||
|
final existingProduct = currentState.selectedProducts.firstWhere(
|
||||||
(p) => p.productId == event.productId,
|
(p) => p.productId == event.productId,
|
||||||
orElse: () => SelectedProduct(productId: event.productId, count: 0,productName: event.productName,product: event.product ),
|
orElse: () => SelectedProduct(
|
||||||
|
productId: event.productId,
|
||||||
|
count: 0,
|
||||||
|
productName: event.productName,
|
||||||
|
product: event.product,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
List<SelectedProduct> updatedProducts;
|
||||||
|
|
||||||
if (event.count > 0) {
|
if (event.count > 0) {
|
||||||
if (!state.contains(existingProduct)) {
|
if (!currentState.selectedProducts.contains(existingProduct)) {
|
||||||
emit([
|
updatedProducts = [
|
||||||
...state,
|
...currentState.selectedProducts,
|
||||||
SelectedProduct(productId: event.productId, count: event.count, productName: event.productName, product: event.product)
|
SelectedProduct(
|
||||||
]);
|
productId: event.productId,
|
||||||
|
count: event.count,
|
||||||
|
productName: event.productName,
|
||||||
|
product: event.product,
|
||||||
|
),
|
||||||
|
];
|
||||||
} else {
|
} else {
|
||||||
final updatedList = state.map((p) {
|
updatedProducts = currentState.selectedProducts.map((p) {
|
||||||
if (p.productId == event.productId) {
|
if (p.productId == event.productId) {
|
||||||
return SelectedProduct(productId: p.productId, count: event.count, productName: p.productName,product: p.product);
|
return SelectedProduct(
|
||||||
|
productId: p.productId,
|
||||||
|
count: event.count,
|
||||||
|
productName: p.productName,
|
||||||
|
product: p.product,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
}).toList();
|
}).toList();
|
||||||
emit(updatedList);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
emit(state.where((p) => p.productId != event.productId).toList());
|
// Remove the product if the count is 0
|
||||||
|
updatedProducts = currentState.selectedProducts
|
||||||
|
.where((p) => p.productId != event.productId)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit the updated state
|
||||||
|
emit(AddDeviceModelLoaded(
|
||||||
|
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/space_model/models/tag_model.dart';
|
||||||
|
|
||||||
|
abstract class AddDeviceModelState extends Equatable {
|
||||||
|
const AddDeviceModelState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddDeviceModelInitial extends AddDeviceModelState {}
|
||||||
|
|
||||||
|
class AddDeviceModelLoading extends AddDeviceModelState {}
|
||||||
|
|
||||||
|
class AddDeviceModelLoaded extends AddDeviceModelState {
|
||||||
|
final List<SelectedProduct> selectedProducts;
|
||||||
|
final List<TagModel> initialTag;
|
||||||
|
|
||||||
|
const AddDeviceModelLoaded({
|
||||||
|
required this.selectedProducts,
|
||||||
|
required this.initialTag,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [selectedProducts, initialTag];
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddDeviceModelError extends AddDeviceModelState {
|
||||||
|
final String errorMessage;
|
||||||
|
|
||||||
|
const AddDeviceModelError(this.errorMessage);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [errorMessage];
|
||||||
|
}
|
||||||
@ -1,11 +1,16 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
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/product_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
|
||||||
|
|
||||||
abstract class AddDeviceTypeModelEvent extends Equatable {
|
abstract class AddDeviceTypeModelEvent extends Equatable {
|
||||||
|
const AddDeviceTypeModelEvent();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [];
|
List<Object> get props => [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class UpdateProductCountEvent extends AddDeviceTypeModelEvent {
|
class UpdateProductCountEvent extends AddDeviceTypeModelEvent {
|
||||||
final String productId;
|
final String productId;
|
||||||
final int count;
|
final int count;
|
||||||
@ -17,3 +22,17 @@ class UpdateProductCountEvent extends AddDeviceTypeModelEvent {
|
|||||||
@override
|
@override
|
||||||
List<Object> get props => [productId, count];
|
List<Object> get props => [productId, count];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class InitializeDeviceTypeModel extends AddDeviceTypeModelEvent {
|
||||||
|
final List<TagModel> initialTags;
|
||||||
|
final List<SelectedProduct> addedProducts;
|
||||||
|
|
||||||
|
const InitializeDeviceTypeModel({
|
||||||
|
this.initialTags = const [],
|
||||||
|
required this.addedProducts,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [initialTags, addedProducts];
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
|
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
|
||||||
|
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.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/product_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
||||||
@ -9,28 +10,35 @@ import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_
|
|||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_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/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_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart';
|
import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_state.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_type_model_event.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart';
|
import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/scrollable_grid_view_widget.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class AddDeviceTypeModelWidget extends StatelessWidget {
|
class AddDeviceTypeModelWidget extends StatelessWidget {
|
||||||
final List<ProductModel>? products;
|
final List<ProductModel>? products;
|
||||||
final ValueChanged<List<SelectedProduct>>? onProductsSelected;
|
|
||||||
final List<SelectedProduct>? initialSelectedProducts;
|
final List<SelectedProduct>? initialSelectedProducts;
|
||||||
final List<SubspaceTemplateModel>? subspaces;
|
final List<SubspaceTemplateModel>? subspaces;
|
||||||
final List<TagModel>? spaceTagModels;
|
final List<TagModel>? spaceTagModels;
|
||||||
final List<String>? allTags;
|
final List<String>? allTags;
|
||||||
final String spaceName;
|
final String spaceName;
|
||||||
|
final bool isCreate;
|
||||||
|
final List<String>? otherSpaceModels;
|
||||||
|
final BuildContext? pageContext;
|
||||||
|
final SpaceTemplateModel? spaceModel;
|
||||||
|
|
||||||
const AddDeviceTypeModelWidget(
|
const AddDeviceTypeModelWidget(
|
||||||
{super.key,
|
{super.key,
|
||||||
this.products,
|
this.products,
|
||||||
this.initialSelectedProducts,
|
this.initialSelectedProducts,
|
||||||
this.onProductsSelected,
|
|
||||||
this.subspaces,
|
this.subspaces,
|
||||||
this.allTags,
|
this.allTags,
|
||||||
this.spaceTagModels,
|
this.spaceTagModels,
|
||||||
required this.spaceName});
|
required this.spaceName,
|
||||||
|
required this.isCreate,
|
||||||
|
this.pageContext,
|
||||||
|
this.otherSpaceModels,
|
||||||
|
this.spaceModel});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -42,12 +50,22 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
|
|||||||
: 3;
|
: 3;
|
||||||
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (_) => AddDeviceTypeModelBloc(initialSelectedProducts ?? []),
|
create: (_) => AddDeviceTypeModelBloc()
|
||||||
|
..add(InitializeDeviceTypeModel(
|
||||||
|
initialTags: spaceTagModels ?? [],
|
||||||
|
addedProducts: initialSelectedProducts ?? [],
|
||||||
|
)),
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text('Add Devices'),
|
title: const Text('Add Devices'),
|
||||||
backgroundColor: ColorsManager.whiteColors,
|
backgroundColor: ColorsManager.whiteColors,
|
||||||
content: SingleChildScrollView(
|
content: BlocBuilder<AddDeviceTypeModelBloc, AddDeviceModelState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state is AddDeviceModelLoading) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
if (state is AddDeviceModelLoaded) {
|
||||||
|
return SingleChildScrollView(
|
||||||
child: Container(
|
child: Container(
|
||||||
width: size.width * 0.9,
|
width: size.width * 0.9,
|
||||||
height: size.height * 0.65,
|
height: size.height * 0.65,
|
||||||
@ -57,14 +75,23 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
child: ScrollableGridViewWidget(
|
child: ScrollableGridViewWidget(
|
||||||
products: products, crossAxisCount: crossAxisCount),
|
isCreate: isCreate,
|
||||||
|
products: products,
|
||||||
|
crossAxisCount: crossAxisCount,
|
||||||
|
initialProductCounts: state.selectedProducts,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const SizedBox();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
Row(
|
Row(
|
||||||
@ -73,31 +100,68 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
|
|||||||
CancelButton(
|
CancelButton(
|
||||||
label: 'Cancel',
|
label: 'Cancel',
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
if (isCreate) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
await showDialog(
|
await showDialog(
|
||||||
barrierDismissible: false,
|
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => CreateSpaceModelDialog(
|
builder: (BuildContext dialogContext) {
|
||||||
|
return CreateSpaceModelDialog(
|
||||||
products: products,
|
products: products,
|
||||||
allTags: allTags,
|
allTags: allTags,
|
||||||
|
pageContext: pageContext,
|
||||||
|
otherSpaceModels: otherSpaceModels,
|
||||||
spaceModel: SpaceTemplateModel(
|
spaceModel: SpaceTemplateModel(
|
||||||
modelName: spaceName,
|
modelName: spaceName,
|
||||||
subspaceModels: subspaces,
|
tags: spaceModel?.tags ?? [],
|
||||||
tags: spaceTagModels,
|
uuid: spaceModel?.uuid,
|
||||||
)),
|
internalId: spaceModel?.internalId,
|
||||||
|
subspaceModels: subspaces),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
ActionButton(
|
} else {
|
||||||
label: 'Continue',
|
final initialTags = generateInitialTags(
|
||||||
backgroundColor: ColorsManager.secondaryColor,
|
spaceTagModels: spaceTagModels,
|
||||||
foregroundColor: ColorsManager.whiteColors,
|
subspaces: subspaces,
|
||||||
onPressed: () async {
|
);
|
||||||
final currentState =
|
|
||||||
context.read<AddDeviceTypeModelBloc>().state;
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
|
|
||||||
if (currentState.isNotEmpty) {
|
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,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 140,
|
||||||
|
child:
|
||||||
|
BlocBuilder<AddDeviceTypeModelBloc, AddDeviceModelState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final isDisabled = state is AddDeviceModelLoaded &&
|
||||||
|
state.selectedProducts.isEmpty;
|
||||||
|
|
||||||
|
return DefaultButton(
|
||||||
|
backgroundColor: ColorsManager.secondaryColor,
|
||||||
|
foregroundColor: isDisabled
|
||||||
|
? ColorsManager.whiteColorsWithOpacity
|
||||||
|
: ColorsManager.whiteColors,
|
||||||
|
borderRadius: 10,
|
||||||
|
onPressed: isDisabled
|
||||||
|
? null // Disable the button
|
||||||
|
: () async {
|
||||||
|
if (state is AddDeviceModelLoaded &&
|
||||||
|
state.selectedProducts.isNotEmpty) {
|
||||||
final initialTags = generateInitialTags(
|
final initialTags = generateInitialTags(
|
||||||
spaceTagModels: spaceTagModels,
|
spaceTagModels: spaceTagModels,
|
||||||
subspaces: subspaces,
|
subspaces: subspaces,
|
||||||
@ -106,27 +170,35 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
|
|||||||
final dialogTitle = initialTags.isNotEmpty
|
final dialogTitle = initialTags.isNotEmpty
|
||||||
? 'Edit Device'
|
? 'Edit Device'
|
||||||
: 'Assign Tags';
|
: 'Assign Tags';
|
||||||
|
Navigator.of(context).pop();
|
||||||
await showDialog<bool>(
|
await showDialog<bool>(
|
||||||
barrierDismissible: false,
|
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AssignTagModelsDialog(
|
builder: (context) => AssignTagModelsDialog(
|
||||||
products: products,
|
products: products,
|
||||||
subspaces: subspaces,
|
subspaces: subspaces,
|
||||||
addedProducts: currentState,
|
addedProducts: state.selectedProducts,
|
||||||
allTags: allTags,
|
allTags: allTags,
|
||||||
spaceName: spaceName,
|
spaceName: spaceName,
|
||||||
initialTags: initialTags,
|
initialTags: state.initialTag,
|
||||||
|
otherSpaceModels: otherSpaceModels,
|
||||||
title: dialogTitle,
|
title: dialogTitle,
|
||||||
|
spaceModel: spaceModel,
|
||||||
|
pageContext: pageContext,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
child: const Text('Next'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
));
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<TagModel> generateInitialTags({
|
List<TagModel> generateInitialTags({
|
||||||
|
|||||||
@ -13,12 +13,13 @@ import 'package:syncrow_web/utils/constants/assets.dart';
|
|||||||
class DeviceTypeTileWidget extends StatelessWidget {
|
class DeviceTypeTileWidget extends StatelessWidget {
|
||||||
final ProductModel product;
|
final ProductModel product;
|
||||||
final List<SelectedProduct> productCounts;
|
final List<SelectedProduct> productCounts;
|
||||||
|
final bool isCreate;
|
||||||
|
|
||||||
const DeviceTypeTileWidget({
|
const DeviceTypeTileWidget(
|
||||||
super.key,
|
{super.key,
|
||||||
required this.product,
|
required this.product,
|
||||||
required this.productCounts,
|
required this.productCounts,
|
||||||
});
|
required this.isCreate});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -48,6 +49,7 @@ class DeviceTypeTileWidget extends StatelessWidget {
|
|||||||
DeviceNameWidget(name: product.name),
|
DeviceNameWidget(name: product.name),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
CounterWidget(
|
CounterWidget(
|
||||||
|
isCreate: isCreate,
|
||||||
initialCount: selectedProduct.count,
|
initialCount: selectedProduct.count,
|
||||||
onCountChanged: (newCount) {
|
onCountChanged: (newCount) {
|
||||||
context.read<AddDeviceTypeModelBloc>().add(
|
context.read<AddDeviceTypeModelBloc>().add(
|
||||||
|
|||||||
@ -3,18 +3,21 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_bloc.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/widgets/device_type_tile_widget.dart';
|
import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/device_type_tile_widget.dart';
|
||||||
|
|
||||||
class ScrollableGridViewWidget extends StatelessWidget {
|
class ScrollableGridViewWidget extends StatelessWidget {
|
||||||
final List<ProductModel>? products;
|
final List<ProductModel>? products;
|
||||||
final int crossAxisCount;
|
final int crossAxisCount;
|
||||||
final List<SelectedProduct>? initialProductCounts;
|
final List<SelectedProduct>? initialProductCounts;
|
||||||
|
final bool isCreate;
|
||||||
|
|
||||||
const ScrollableGridViewWidget({
|
const ScrollableGridViewWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.products,
|
required this.products,
|
||||||
required this.crossAxisCount,
|
required this.crossAxisCount,
|
||||||
this.initialProductCounts,
|
this.initialProductCounts,
|
||||||
|
required this.isCreate
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -24,8 +27,12 @@ class ScrollableGridViewWidget extends StatelessWidget {
|
|||||||
return Scrollbar(
|
return Scrollbar(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
thumbVisibility: true,
|
thumbVisibility: true,
|
||||||
child: BlocBuilder<AddDeviceTypeModelBloc, List<SelectedProduct>>(
|
child: BlocBuilder<AddDeviceTypeModelBloc, AddDeviceModelState>(
|
||||||
builder: (context, productCounts) {
|
builder: (context, state) {
|
||||||
|
final productCounts = state is AddDeviceModelLoaded
|
||||||
|
? state.selectedProducts
|
||||||
|
: <SelectedProduct>[];
|
||||||
|
|
||||||
return GridView.builder(
|
return GridView.builder(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
@ -42,6 +49,7 @@ class ScrollableGridViewWidget extends StatelessWidget {
|
|||||||
|
|
||||||
return DeviceTypeTileWidget(
|
return DeviceTypeTileWidget(
|
||||||
product: product,
|
product: product,
|
||||||
|
isCreate: isCreate,
|
||||||
productCounts: initialProductCount != null
|
productCounts: initialProductCount != null
|
||||||
? [...productCounts, initialProductCount]
|
? [...productCounts, initialProductCount]
|
||||||
: productCounts,
|
: productCounts,
|
||||||
|
|||||||
@ -12,4 +12,33 @@ class HomeApi {
|
|||||||
});
|
});
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future fetchTerms() async {
|
||||||
|
final response = await HTTPService().get(
|
||||||
|
path: ApiEndpoints.terms,
|
||||||
|
showServerMessage: true,
|
||||||
|
expectedResponseModel: (json) {
|
||||||
|
return json['data'];
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future fetchPolicy() async {
|
||||||
|
final response = await HTTPService().get(
|
||||||
|
path: ApiEndpoints.policy,
|
||||||
|
showServerMessage: true,
|
||||||
|
expectedResponseModel: (json) {
|
||||||
|
return json['data'];
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future confirmUserAgreements(uuid) async {
|
||||||
|
final response = await HTTPService().patch(
|
||||||
|
path: ApiEndpoints.userAgreements.replaceAll('{userUuid}', uuid!),
|
||||||
|
expectedResponseModel: (json) {
|
||||||
|
return json['data'];
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subspace_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/spaces_management/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:syncrow_web/services/api/http_service.dart';
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||||
import 'package:syncrow_web/utils/constants/temp_const.dart';
|
import 'package:syncrow_web/utils/constants/temp_const.dart';
|
||||||
@ -19,23 +22,26 @@ class CommunitySpaceManagementApi {
|
|||||||
.replaceAll('{projectId}', TempConst.projectId),
|
.replaceAll('{projectId}', TempConst.projectId),
|
||||||
queryParameters: {'page': page},
|
queryParameters: {'page': page},
|
||||||
expectedResponseModel: (json) {
|
expectedResponseModel: (json) {
|
||||||
List<dynamic> jsonData = json['data'];
|
try {
|
||||||
|
List<dynamic> jsonData = json['data'] ?? [];
|
||||||
hasNext = json['hasNext'] ?? false;
|
hasNext = json['hasNext'] ?? false;
|
||||||
int currentPage = json['page'] ?? 1;
|
int currentPage = json['page'] ?? 1;
|
||||||
List<CommunityModel> communityList = jsonData.map((jsonItem) {
|
List<CommunityModel> communityList = jsonData.map((jsonItem) {
|
||||||
return CommunityModel.fromJson(jsonItem);
|
return CommunityModel.fromJson(jsonItem);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
allCommunities.addAll(communityList);
|
allCommunities.addAll(communityList);
|
||||||
page = currentPage + 1;
|
page = currentPage + 1;
|
||||||
return communityList;
|
return communityList;
|
||||||
|
} catch (_) {
|
||||||
|
hasNext = false;
|
||||||
|
return [];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return allCommunities;
|
return allCommunities;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Error fetching communities: $e');
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,15 +164,17 @@ class CommunitySpaceManagementApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SpaceModel?> createSpace({
|
Future<SpaceModel?> createSpace(
|
||||||
required String communityId,
|
{required String communityId,
|
||||||
required String name,
|
required String name,
|
||||||
String? parentId,
|
String? parentId,
|
||||||
String? direction,
|
String? direction,
|
||||||
bool isPrivate = false,
|
bool isPrivate = false,
|
||||||
required Offset position,
|
required Offset position,
|
||||||
|
String? spaceModelUuid,
|
||||||
String? icon,
|
String? icon,
|
||||||
}) async {
|
List<CreateTagBodyModel>? tags,
|
||||||
|
List<CreateSubspaceModel>? subspaces}) async {
|
||||||
try {
|
try {
|
||||||
final body = {
|
final body = {
|
||||||
'spaceName': name,
|
'spaceName': name,
|
||||||
@ -179,6 +187,13 @@ class CommunitySpaceManagementApi {
|
|||||||
if (parentId != null) {
|
if (parentId != null) {
|
||||||
body['parentUuid'] = parentId;
|
body['parentUuid'] = parentId;
|
||||||
}
|
}
|
||||||
|
if (tags != null) {
|
||||||
|
body['tags'] = tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spaceModelUuid != null) body['spaceModelUuid'] = spaceModelUuid;
|
||||||
|
|
||||||
|
if (subspaces != null) body['subspaces'] = subspaces;
|
||||||
final response = await HTTPService().post(
|
final response = await HTTPService().post(
|
||||||
path: ApiEndpoints.createSpace
|
path: ApiEndpoints.createSpace
|
||||||
.replaceAll('{communityId}', communityId)
|
.replaceAll('{communityId}', communityId)
|
||||||
@ -260,7 +275,6 @@ class CommunitySpaceManagementApi {
|
|||||||
.replaceAll('{communityId}', communityId)
|
.replaceAll('{communityId}', communityId)
|
||||||
.replaceAll('{projectId}', TempConst.projectId),
|
.replaceAll('{projectId}', TempConst.projectId),
|
||||||
expectedResponseModel: (json) {
|
expectedResponseModel: (json) {
|
||||||
print(json);
|
|
||||||
final spaceModels = (json['data'] as List)
|
final spaceModels = (json['data'] as List)
|
||||||
.map((spaceJson) => SpaceModel.fromJson(spaceJson))
|
.map((spaceJson) => SpaceModel.fromJson(spaceJson))
|
||||||
.toList();
|
.toList();
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
|
||||||
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
|
||||||
import 'package:syncrow_web/services/api/http_service.dart';
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
@ -7,38 +6,22 @@ import 'package:syncrow_web/utils/constants/temp_const.dart';
|
|||||||
|
|
||||||
class SpaceModelManagementApi {
|
class SpaceModelManagementApi {
|
||||||
Future<List<SpaceTemplateModel>> listSpaceModels({int page = 1}) async {
|
Future<List<SpaceTemplateModel>> listSpaceModels({int page = 1}) async {
|
||||||
try {
|
final response = await HTTPService().get(
|
||||||
List<SpaceTemplateModel> spaceModels = [];
|
|
||||||
bool hasNext = true;
|
|
||||||
while (hasNext) {
|
|
||||||
await HTTPService().get(
|
|
||||||
path: ApiEndpoints.listSpaceModels
|
path: ApiEndpoints.listSpaceModels
|
||||||
.replaceAll('{projectId}', TempConst.projectId),
|
.replaceAll('{projectId}', TempConst.projectId),
|
||||||
queryParameters: {'page': page},
|
queryParameters: {'page': page},
|
||||||
expectedResponseModel: (json) {
|
expectedResponseModel: (json) {
|
||||||
List<dynamic> jsonData = json['data'];
|
List<dynamic> jsonData = json['data'];
|
||||||
hasNext = json['hasNext'] ?? false;
|
return jsonData.map((jsonItem) {
|
||||||
int currentPage = json['page'] ?? 1;
|
|
||||||
List<SpaceTemplateModel> spaceModelList = jsonData.map((jsonItem) {
|
|
||||||
return SpaceTemplateModel.fromJson(jsonItem);
|
return SpaceTemplateModel.fromJson(jsonItem);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
spaceModels.addAll(spaceModelList);
|
|
||||||
page = currentPage + 1;
|
|
||||||
return spaceModelList;
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
return response;
|
||||||
return spaceModels;
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('Error fetching space models: $e');
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SpaceTemplateModel?> createSpaceModel(
|
Future<SpaceTemplateModel?> createSpaceModel(
|
||||||
CreateSpaceTemplateBodyModel spaceModel) async {
|
CreateSpaceTemplateBodyModel spaceModel) async {
|
||||||
try {
|
|
||||||
final response = await HTTPService().post(
|
final response = await HTTPService().post(
|
||||||
path: ApiEndpoints.createSpaceModel
|
path: ApiEndpoints.createSpaceModel
|
||||||
.replaceAll('{projectId}', TempConst.projectId),
|
.replaceAll('{projectId}', TempConst.projectId),
|
||||||
@ -49,29 +32,32 @@ class SpaceModelManagementApi {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
|
||||||
debugPrint('Error creating space model: $e');
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 {
|
Future<SpaceTemplateModel?> getSpaceModel(String spaceModelUuid) async {
|
||||||
try {
|
|
||||||
final response = await HTTPService().get(
|
final response = await HTTPService().get(
|
||||||
path: ApiEndpoints.getSpaceModel
|
path: ApiEndpoints.getSpaceModel
|
||||||
.replaceAll('{projectId}', TempConst.projectId)
|
.replaceAll('{projectId}', TempConst.projectId)
|
||||||
.replaceAll('{spaceModelUuid}', spaceModelUuid),
|
.replaceAll('{spaceModelUuid}', spaceModelUuid),
|
||||||
showServerMessage: true,
|
showServerMessage: true,
|
||||||
expectedResponseModel: (json) {
|
expectedResponseModel: (json) {
|
||||||
debugPrint('Response JSON: $json');
|
|
||||||
|
|
||||||
return SpaceTemplateModel.fromJson(json['data']);
|
return SpaceTemplateModel.fromJson(json['data']);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
|
||||||
debugPrint('Error getting space model: $e');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,8 +71,8 @@ class UserPermissionApi {
|
|||||||
"firstName": firstName,
|
"firstName": firstName,
|
||||||
"lastName": lastName,
|
"lastName": lastName,
|
||||||
"email": email,
|
"email": email,
|
||||||
"jobTitle": jobTitle != '' ? jobTitle : " ",
|
"jobTitle": jobTitle != '' ? jobTitle : null,
|
||||||
"phoneNumber": phoneNumber != '' ? phoneNumber : " ",
|
"phoneNumber": phoneNumber != '' ? phoneNumber : null,
|
||||||
"roleUuid": roleUuid,
|
"roleUuid": roleUuid,
|
||||||
"projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c",
|
"projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c",
|
||||||
"spaceUuids": spaceUuids,
|
"spaceUuids": spaceUuids,
|
||||||
@ -119,13 +119,8 @@ class UserPermissionApi {
|
|||||||
);
|
);
|
||||||
return response ?? 'Unknown error occurred';
|
return response ?? 'Unknown error occurred';
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
if (e.response != null) {
|
|
||||||
final errorMessage = e.response?.data['error'];
|
final errorMessage = e.response?.data['error'];
|
||||||
return errorMessage is String
|
return errorMessage;
|
||||||
? errorMessage
|
|
||||||
: 'Error occurred while checking email';
|
|
||||||
}
|
|
||||||
return 'Error occurred while checking email';
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return e.toString();
|
return e.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
15
lib/utils/asset_validator.dart
Normal file
15
lib/utils/asset_validator.dart
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class AssetValidator {
|
||||||
|
static Future<bool> isValidAsset(String? assetPath) async {
|
||||||
|
if (assetPath == null || assetPath.isEmpty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await rootBundle.load(assetPath);
|
||||||
|
return true;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -68,6 +68,7 @@ abstract class ColorsManager {
|
|||||||
static const Color disabledRedText = Color(0xFF890002);
|
static const Color disabledRedText = Color(0xFF890002);
|
||||||
static const Color invitedOrange = Color(0xFFFFE193);
|
static const Color invitedOrange = Color(0xFFFFE193);
|
||||||
static const Color invitedOrangeText = Color(0xFFFFBF00);
|
static const Color invitedOrangeText = Color(0xFFFFBF00);
|
||||||
|
static const Color lightGrayBorderColor = Color(0xB2D5D5D5);
|
||||||
//background: #F8F8F8;
|
//background: #F8F8F8;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -101,7 +101,10 @@ abstract class ApiEndpoints {
|
|||||||
//space model
|
//space model
|
||||||
static const String listSpaceModels = '/projects/{projectId}/space-models';
|
static const String listSpaceModels = '/projects/{projectId}/space-models';
|
||||||
static const String createSpaceModel = '/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 roleTypes = '/role/types';
|
||||||
static const String permission = '/permission/{roleUuid}';
|
static const String permission = '/permission/{roleUuid}';
|
||||||
@ -114,6 +117,7 @@ abstract class ApiEndpoints {
|
|||||||
static const String deleteUser = '/invite-user/{inviteUserUuid}';
|
static const String deleteUser = '/invite-user/{inviteUserUuid}';
|
||||||
static const String changeUserStatus =
|
static const String changeUserStatus =
|
||||||
'/invite-user/{invitedUserUuid}/disable';
|
'/invite-user/{invitedUserUuid}/disable';
|
||||||
|
static const String terms = '/terms';
|
||||||
// static const String updateAutomation = '/automation/{automationId}';
|
static const String policy = '/policy';
|
||||||
|
static const String userAgreements = '/user/agreements/web/{userUuid}';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -397,5 +397,6 @@ class Assets {
|
|||||||
static const String filterTableIcon = 'assets/icons/filter_table_icon.svg';
|
static const String filterTableIcon = 'assets/icons/filter_table_icon.svg';
|
||||||
static const String ZtoAIcon = 'assets/icons/ztoa_icon.png';
|
static const String ZtoAIcon = 'assets/icons/ztoa_icon.png';
|
||||||
static const String AtoZIcon = 'assets/icons/atoz_icon.png';
|
static const String AtoZIcon = 'assets/icons/atoz_icon.png';
|
||||||
|
static const String link = 'assets/icons/link.svg';
|
||||||
}
|
}
|
||||||
//user_management.svg
|
//user_management.svg
|
||||||
|
|||||||
@ -7,9 +7,13 @@
|
|||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||||
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||||
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||||
|
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
flutter_secure_storage_linux
|
flutter_secure_storage_linux
|
||||||
|
url_launcher_linux
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
|||||||
@ -8,9 +8,11 @@ import Foundation
|
|||||||
import flutter_secure_storage_macos
|
import flutter_secure_storage_macos
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
|
import url_launcher_macos
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,6 +54,8 @@ dependencies:
|
|||||||
time_picker_spinner: ^1.0.0
|
time_picker_spinner: ^1.0.0
|
||||||
intl_phone_field: ^3.2.0
|
intl_phone_field: ^3.2.0
|
||||||
number_pagination: ^1.1.6
|
number_pagination: ^1.1.6
|
||||||
|
url_launcher: ^6.2.5
|
||||||
|
flutter_html: ^3.0.0-beta.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@ -7,8 +7,11 @@
|
|||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||||
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||||
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user