Compare commits

..

62 Commits

Author SHA1 Message Date
6045158432 Reset Selected space 2025-03-24 15:32:02 +03:00
7e06dd76cf create routine dialog flow 2025-03-24 13:57:53 +03:00
7e1c2ba712 routines and automation Toggle 2025-03-21 17:14:05 +03:00
c0d53fdf5c Update Automation Status 2025-03-19 12:24:55 +03:00
5e05b20f68 Merge pull request #118 from SyncrowIOT/SP-1318
Sp 1318
2025-03-17 09:34:16 +04:00
95730cc2ba Merge pull request #119 from SyncrowIOT/SP-1327
append space id to list
2025-03-17 09:33:28 +04:00
a5a37f3841 append space id to list 2025-03-14 14:10:03 +04:00
e0486deecb changed endpoint for automation update 2025-03-14 12:16:32 +04:00
bc64efa557 updated delete automation endpoint 2025-03-14 12:12:15 +04:00
2a065efc0e replaced endpoint for get automaiton by id 2025-03-14 12:09:14 +04:00
7ae3e4a933 updated endpoint for creating automations 2025-03-14 11:54:47 +04:00
2a0f6a4596 updated endpoint of get automation by space 2025-03-14 11:50:18 +04:00
5b2822f973 Merge pull request #117 from SyncrowIOT/SP-1318
Sp 1318
2025-03-13 14:51:54 +04:00
70d31f5351 removed print 2025-03-13 14:51:09 +04:00
c39c693755 remove unused import 2025-03-13 14:37:39 +04:00
8ed6980264 resposnive text length of space model name 2025-03-13 13:51:52 +04:00
16df75eb49 fixed space update 2025-03-13 13:14:31 +04:00
80c294f09c fix real time listenToChanges 2025-03-12 16:03:01 +03:00
c806b5f59d Added a background color for the space tree 2025-03-11 23:54:39 +03:00
dd01a7fddb Pulled latest changes 2025-03-11 23:42:42 +03:00
b563cc378e Added current temp to AC functions 2025-03-11 23:41:21 +03:00
2140b7eee3 Merge pull request #114 from SyncrowIOT:bugfix/update-space
update space with a space model
2025-03-11 22:08:06 +04:00
7de9e25ed5 Merge branch 'dev' of https://github.com/SyncrowIOT/web into bugfix/update-space 2025-03-11 22:07:50 +04:00
123949aa86 update space with a space model 2025-03-11 22:05:36 +04:00
6d554c5953 Merge pull request #113 from SyncrowIOT/bugfix/remove-link
fixed issue in removing link
2025-03-10 16:55:02 +04:00
5ed87a5794 fixed issue in removing link 2025-03-10 11:47:00 +04:00
168c997240 Merge pull request #112 from SyncrowIOT:bugfix/empty-space-model
fixed issue in update
2025-03-09 17:32:38 +04:00
de65f79ccf Merge branch 'dev' of https://github.com/SyncrowIOT/web into bugfix/empty-space-model 2025-03-09 17:32:06 +04:00
eb0490fb16 fixed issue in update 2025-03-09 17:31:36 +04:00
a73fc04712 Merge pull request #110 from SyncrowIOT/SP-1297
fix model and SpaceModelCardWidget and LinkSpaceModelSpacesDialog
2025-03-09 16:11:18 +03:00
dee07ebb06 fix LinkingSuccessful 2025-03-09 16:01:04 +03:00
5237f3ae5b Merge pull request #111 from SyncrowIOT/bugfix/empty-space-model
fix the circular progress on empty space model
2025-03-09 16:49:36 +04:00
e017633b9b fixed space model 2025-03-09 16:48:42 +04:00
e8e5e9bcb7 fix LoadedSpaceView 2025-03-09 14:54:13 +03:00
a1d15c9cea fix the circular progress on empty space model 2025-03-09 15:53:55 +04:00
16c006e7a9 Merge pull request #109 from SyncrowIOT/feat/fix-update-on-community-tree
Feat/fix-update-on-community-tree
2025-03-09 14:06:48 +04:00
24280549ec revert back color change 2025-03-09 14:06:33 +04:00
8359642a1a Merge branch 'dev' into SP-1297 2025-03-09 12:03:34 +03:00
ada19a5992 fetch communities and spaces on space model update 2025-03-09 10:47:09 +04:00
c6702d4d5f Merge branch 'dev' of https://github.com/SyncrowIOT/web into dev 2025-03-09 10:29:05 +04:00
f78e2e2d36 Merge branch 'feat/update-space-model' of https://github.com/SyncrowIOT/web into dev 2025-03-09 10:26:34 +04:00
83b9555920 added padding to space tree on community structure 2025-03-09 10:26:30 +04:00
8af24e575c fix model and SpaceModelCardWidget and LinkSpaceModelSpacesDialog 2025-03-08 09:57:35 +03:00
4301d7f62f Merge pull request #108 from SyncrowIOT/feat/update-space-model
Feat/update space model
2025-03-07 11:20:29 +04:00
788d36541b revert settings.json 2025-03-07 11:19:46 +04:00
44cf1cec69 resolved conflict 2025-03-07 11:18:36 +04:00
acb7ed5192 Merge branch 'dev' of https://github.com/SyncrowIOT/web into feat/update-space-model 2025-03-07 00:30:13 +04:00
1b8a87e942 initcommunity on save space 2025-03-07 00:01:35 +04:00
fa39182386 Merge pull request #106 from SyncrowIOT/link_space_model_spaces
Link space model spaces
2025-03-06 12:54:38 +03:00
efccac4d19 removed unused widget 2025-03-06 13:29:28 +04:00
096317fce8 Merge branch 'dev' of https://github.com/SyncrowIOT/web into feat/update-space-model 2025-03-06 13:19:53 +04:00
d624dd767b add tags 2025-03-06 12:52:41 +04:00
9795517a3f Merge branch 'SP-1218' into feat/update-space-model 2025-03-06 12:01:12 +04:00
fecab17cbe updated body for update space model 2025-03-06 11:41:36 +04:00
d2ff909bf2 updated tag dialogue selection 2025-03-05 22:24:45 +04:00
5cea8eddb3 added project tags to all dialogs 2025-03-05 21:26:36 +04:00
213ec329c0 passed tags to space model page 2025-03-05 17:16:00 +04:00
c9427b35be added tags 2025-03-05 17:12:02 +04:00
a7995bb2b8 added tag api 2025-03-05 16:59:58 +04:00
20474835dd Merge branch 'dev' of https://github.com/SyncrowIOT/web into feat/update-space-model 2025-03-05 16:42:43 +04:00
5233c1b38e format settings 2025-03-05 16:41:57 +04:00
d88ae9ea15 removed tag model and use tag only 2025-03-05 14:19:58 +04:00
86 changed files with 2380 additions and 1436 deletions

View File

@ -0,0 +1,17 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_6675_32326)">
<path d="M18 3C9.71584 3 3 9.71572 3 18C3 26.2843 9.71584 33 18 33C26.2842 33 33 26.2843 33 18C33 9.71572 26.2842 3 18 3ZM23.1844 18.7951L15.6844 23.4826C15.5326 23.5774 15.3601 23.625 15.1875 23.625C15.0312 23.625 14.8746 23.5861 14.7329 23.5073C14.4349 23.3421 14.25 23.0285 14.25 22.6875V13.3125C14.25 12.9715 14.4349 12.6579 14.7329 12.4927C15.0309 12.3265 15.3953 12.3366 15.6844 12.5174L23.1844 17.2049C23.4584 17.3766 23.625 17.6769 23.625 18C23.625 18.3231 23.4584 18.6235 23.1844 18.7951Z" fill="#F4F4F4"/>
</g>
<defs>
<filter id="filter0_d_6675_32326" x="0" y="0" width="36" height="36" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="1.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6675_32326"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6675_32326" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,30 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_7280_5211)">
<circle cx="18" cy="18" r="15" fill="#F4F4F4"/>
</g>
<g filter="url(#filter1_i_7280_5211)">
<path d="M25.1663 13.187C24.8231 12.8439 24.2666 12.8439 23.9234 13.1871L16.1621 20.9484L12.0766 16.8628C11.7334 16.5196 11.1768 16.5196 10.8336 16.8628C10.4904 17.206 10.4904 17.7625 10.8336 18.1057L15.5406 22.8127C15.7122 22.9844 15.9372 23.0701 16.1621 23.0701C16.3869 23.0701 16.6119 22.9843 16.7835 22.8127L25.1663 14.43C25.5095 14.0868 25.5095 13.5303 25.1663 13.187Z" fill="white"/>
</g>
<defs>
<filter id="filter0_d_7280_5211" x="0" y="0" width="36" height="36" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="1.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_7280_5211"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_7280_5211" result="shape"/>
</filter>
<filter id="filter1_i_7280_5211" x="10.5762" y="12.9297" width="14.8475" height="10.1406" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="1.5"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_7280_5211"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,4 @@
<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.13597 13.8173C7.21711 13.9314 7.35373 14 7.5 14C7.64627 14 7.78289 13.9315 7.86403 13.8173C8.89942 12.3614 10.4245 10.5632 11.4872 8.73444C12.3369 7.27221 12.75 6.02509 12.75 4.92188C12.75 2.20795 10.3948 0 7.5 0C4.60515 0 2.25 2.20795 2.25 4.92188C2.25 6.02509 2.66309 7.27221 3.51283 8.73444C4.57476 10.5618 6.10271 12.3644 7.13597 13.8173ZM7.5 0.820312C9.91237 0.820312 11.875 2.66027 11.875 4.92188C11.875 5.88448 11.4968 7.00323 10.7188 8.342C9.80277 9.91834 8.49557 11.5174 7.5 12.8617C6.50457 11.5176 5.19729 9.91843 4.2812 8.342C3.5032 7.00323 3.125 5.88448 3.125 4.92188C3.125 2.66027 5.08763 0.820312 7.5 0.820312Z" fill="#999999"/>
<path d="M7.5 7.38281C8.94742 7.38281 10.125 6.27884 10.125 4.92188C10.125 3.56491 8.94742 2.46094 7.5 2.46094C6.05257 2.46094 4.875 3.56491 4.875 4.92188C4.875 6.27884 6.05257 7.38281 7.5 7.38281ZM7.5 3.28125C8.46495 3.28125 9.25 4.01723 9.25 4.92188C9.25 5.82652 8.46495 6.5625 7.5 6.5625C6.53505 6.5625 5.75 5.82652 5.75 4.92188C5.75 4.01723 6.53505 3.28125 7.5 3.28125Z" fill="#999999"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,21 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_6702_36698)">
<circle cx="20" cy="20" r="15" fill="#F4F4F4"/>
<path d="M26.4 13.1094H23.7333V12.582C23.7333 11.7097 23.0156 11 22.1333 11H17.8667C16.9844 11 16.2667 11.7097 16.2667 12.582V13.1094H13.6C12.7178 13.1094 12 13.8191 12 14.6914C12 15.392 12.4631 15.9873 13.1024 16.1947L14.0537 27.5493C14.1222 28.3628 14.8226 29 15.6481 29H24.3519C25.1775 29 25.8778 28.3628 25.9464 27.5491L26.8976 16.1947C27.5369 15.9873 28 15.392 28 14.6914C28 13.8191 27.2822 13.1094 26.4 13.1094ZM17.3333 12.582C17.3333 12.2913 17.5726 12.0547 17.8667 12.0547H22.1333C22.4274 12.0547 22.6667 12.2913 22.6667 12.582V13.1094H17.3333V12.582ZM24.8833 27.4618C24.8605 27.7329 24.6271 27.9453 24.3519 27.9453H15.6481C15.373 27.9453 15.1395 27.7329 15.1167 27.462L14.1793 16.2734H25.8207L24.8833 27.4618ZM26.4 15.2188H13.6C13.3059 15.2188 13.0667 14.9822 13.0667 14.6914C13.0667 14.4006 13.3059 14.1641 13.6 14.1641H26.4C26.6941 14.1641 26.9333 14.4006 26.9333 14.6914C26.9333 14.9822 26.6941 15.2188 26.4 15.2188Z" fill="#999999"/>
<path d="M17.8656 26.3307L17.3323 17.8229C17.314 17.5322 17.0596 17.3111 16.767 17.3292C16.473 17.3472 16.2494 17.5974 16.2676 17.8881L16.801 26.396C16.8185 26.6756 17.0533 26.8907 17.3328 26.8907C17.6416 26.8907 17.8846 26.6335 17.8656 26.3307Z" fill="#999999"/>
<path d="M20.0001 17.3281C19.7056 17.3281 19.4668 17.5642 19.4668 17.8555V26.3633C19.4668 26.6545 19.7056 26.8906 20.0001 26.8906C20.2947 26.8906 20.5335 26.6545 20.5335 26.3633V17.8555C20.5335 17.5642 20.2947 17.3281 20.0001 17.3281Z" fill="#999999"/>
<path d="M23.233 17.3292C22.9396 17.3111 22.6859 17.5321 22.6677 17.8229L22.1343 26.3307C22.1162 26.6213 22.3397 26.8716 22.6337 26.8896C22.9278 26.9076 23.1808 26.6865 23.199 26.3959L23.7323 17.8881C23.7505 17.5974 23.527 17.3472 23.233 17.3292Z" fill="#999999"/>
</g>
<defs>
<filter id="filter0_d_6702_36698" x="0" y="0" width="40" height="40" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="2.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6702_36698"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6702_36698" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,25 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_6702_36714)">
<circle cx="20" cy="20" r="15" fill="#F4F4F4"/>
<path d="M21.1979 12.3395L18.26 15.2772C17.7228 15.8146 17.3477 16.4536 17.1345 17.1328C16.437 17.3525 15.798 17.7393 15.2772 18.26L12.3395 21.1979C10.5536 22.9837 10.5534 25.8744 12.3395 27.6605C14.1253 29.4464 17.0161 29.4466 18.8022 27.6605L21.7399 24.7227C22.2773 24.1855 22.6522 23.5465 22.8655 22.8671C23.5631 22.6475 24.202 22.2607 24.7227 21.7399L27.6605 18.8022C29.4464 17.0162 29.4467 14.1256 27.6605 12.3395C25.8746 10.5536 22.984 10.5534 21.1979 12.3395ZM17.266 20.2484C17.4886 20.7914 17.8199 21.2998 18.26 21.7399C18.6877 22.1674 19.1965 22.5054 19.7513 22.7343L16.8137 25.6721C16.1284 26.3572 15.0133 26.3574 14.328 25.6721C13.6427 24.9867 13.6427 23.8717 14.328 23.1864L17.2657 20.2485C17.2659 20.2485 17.2659 20.2485 17.266 20.2484ZM21.2428 24.2256L18.3049 27.1633C16.7939 28.6745 14.3479 28.6747 12.8366 27.1633C11.3254 25.6523 11.3253 23.2062 12.8366 21.695L15.7744 18.7571C16.1166 18.4149 16.5191 18.1412 16.9579 17.9488C16.8924 18.4845 16.9223 19.014 17.0364 19.5199C16.9419 19.5905 16.8524 19.6676 16.7686 19.7514L13.8309 22.6892C12.8715 23.6487 12.8715 25.2097 13.8309 26.1691C14.7903 27.1285 16.3513 27.1285 17.3108 26.1691L20.2485 23.2313C21.2089 22.2709 21.209 20.7119 20.2485 19.7514C19.7355 19.2383 19.6105 18.4924 19.8554 17.8663C20.3734 18.059 20.8488 18.3631 21.2428 18.7571C22.7504 20.2647 22.7505 22.7179 21.2428 24.2256ZM27.1633 18.3049L24.2256 21.2428C23.8834 21.585 23.4809 21.8587 23.0421 22.0511C23.1076 21.5154 23.0777 20.986 22.9637 20.4801C23.058 20.4095 23.1477 20.3323 23.2313 20.2485L26.1692 17.3108C27.1286 16.3514 27.1286 14.7903 26.1692 13.8309C25.3077 12.9695 23.9604 12.8807 22.9987 13.5684C22.8408 13.6813 22.8044 13.9009 22.9173 14.0588C23.0301 14.2168 23.2497 14.2533 23.4077 14.1403C24.1093 13.6386 25.0615 13.7174 25.6721 14.328C26.3574 15.0133 26.3574 16.1283 25.6721 16.8137L22.7342 19.7514C22.7342 19.7514 22.7342 19.7514 22.7341 19.7515C22.5113 19.2085 22.1801 18.7002 21.7399 18.26C21.3124 17.8325 20.8035 17.4945 20.2487 17.2656L21.4465 16.0678C21.5837 15.9306 21.5837 15.708 21.4465 15.5707C21.3091 15.4335 21.0867 15.4335 20.9493 15.5707L19.7514 16.7686C18.7911 17.729 18.7909 19.2879 19.7514 20.2485C20.2645 20.7615 20.3894 21.5076 20.1446 22.1337C19.6266 21.941 19.1511 21.6368 18.7571 21.2427C17.2497 19.7352 17.2495 17.282 18.7571 15.7744L21.695 12.8366C23.2061 11.3254 25.6522 11.3252 27.1633 12.8366C28.6745 14.3476 28.6747 16.7937 27.1633 18.3049Z" fill="#999999"/>
<path d="M22.5443 14.8262C22.5443 15.0204 22.3869 15.1777 22.1929 15.1777C21.9987 15.1777 21.8413 15.0204 21.8413 14.8262C21.8413 14.632 21.9987 14.4746 22.1929 14.4746C22.3869 14.4746 22.5443 14.632 22.5443 14.8262Z" fill="#999999"/>
<path d="M15.7755 15.774C15.9128 15.6368 15.9128 15.4142 15.7755 15.2769L14.2841 13.7855C14.1468 13.6483 13.9243 13.6483 13.787 13.7855C13.6498 13.9228 13.6498 14.1455 13.787 14.2828L15.2784 15.7742C15.4158 15.9114 15.6383 15.9114 15.7755 15.774Z" fill="#999999"/>
<path d="M12.3378 16.9062C12.1437 16.9062 11.9863 17.0636 11.9863 17.2577C11.9863 17.4519 12.1437 17.6092 12.3378 17.6092H14.447C14.641 17.6092 14.7984 17.4519 14.7984 17.2577C14.7984 17.0636 14.641 16.9062 14.447 16.9062H12.3378Z" fill="#999999"/>
<path d="M16.9062 12.2314V14.3405C16.9062 14.5346 17.0636 14.6921 17.2577 14.6921C17.4519 14.6921 17.6092 14.5346 17.6092 14.3405V12.2314C17.6092 12.0373 17.4519 11.8799 17.2577 11.8799C17.0636 11.8799 16.9062 12.0373 16.9062 12.2314Z" fill="#999999"/>
<path d="M24.227 24.2259C24.0897 24.3631 24.0897 24.5857 24.227 24.7231L25.7184 26.2145C25.7871 26.2831 25.877 26.3175 25.967 26.3175C26.2772 26.3175 26.4377 25.9397 26.2155 25.7173L24.7242 24.2259C24.5868 24.0887 24.3643 24.0887 24.227 24.2259Z" fill="#999999"/>
<path d="M23.0605 27.7673V25.6581C23.0605 25.464 22.903 25.3066 22.709 25.3066C22.5148 25.3066 22.3574 25.464 22.3574 25.6581V27.7673C22.3574 27.9614 22.5148 28.1187 22.709 28.1187C22.903 28.1187 23.0605 27.9614 23.0605 27.7673Z" fill="#999999"/>
<path d="M27.7693 23.0586C27.9633 23.0586 28.1207 22.9011 28.1207 22.707C28.1207 22.5128 27.9633 22.3555 27.7693 22.3555H25.66C25.466 22.3555 25.3086 22.5128 25.3086 22.707C25.3086 22.9011 25.466 23.0586 25.66 23.0586H27.7693Z" fill="#999999"/>
</g>
<defs>
<filter id="filter0_d_6702_36714" x="0" y="0" width="40" height="40" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="2.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6702_36714"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6702_36714" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -0,0 +1,3 @@
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M43.2614 20.4808C44.1769 21.3963 44.1769 22.8804 43.2614 23.7955L27.5381 39.5192C26.6226 40.4343 25.139 40.4343 24.2235 39.5192L16.7386 32.0338C15.8231 31.1188 15.8231 29.6347 16.7386 28.7196C17.6537 27.8041 19.1377 27.8041 20.0528 28.7196L25.8806 34.5474L39.9467 20.4808C40.8623 19.5657 42.3463 19.5657 43.2614 20.4808ZM60 30C60 46.5825 46.5802 60 30 60C13.4175 60 0 46.5802 0 30C0 13.4175 13.4198 0 30 0C46.5825 0 60 13.4198 60 30ZM55.3125 30C55.3125 16.0085 43.9897 4.6875 30 4.6875C16.0085 4.6875 4.6875 16.0103 4.6875 30C4.6875 43.9915 16.0103 55.3125 30 55.3125C43.9915 55.3125 55.3125 43.9897 55.3125 30Z" fill="#023DFE" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 761 B

View File

@ -1,35 +1,38 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class DialogTextfieldDropdown extends StatefulWidget { class TagDialogTextfieldDropdown extends StatefulWidget {
final List<String> items; final List<Tag> items;
final ValueChanged<String> onSelected; final ValueChanged<Tag> onSelected;
final String? initialValue; final Tag? initialValue;
final String product;
const DialogTextfieldDropdown({ const TagDialogTextfieldDropdown({
Key? key, Key? key,
required this.items, required this.items,
required this.onSelected, required this.onSelected,
this.initialValue, this.initialValue,
required this.product,
}) : super(key: key); }) : super(key: key);
@override @override
_DialogTextfieldDropdownState createState() => _DialogTextfieldDropdownState createState() => _DialogTextfieldDropdownState();
_DialogTextfieldDropdownState();
} }
class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> { class _DialogTextfieldDropdownState extends State<TagDialogTextfieldDropdown> {
bool _isOpen = false; bool _isOpen = false;
OverlayEntry? _overlayEntry; OverlayEntry? _overlayEntry;
final TextEditingController _controller = TextEditingController(); final TextEditingController _controller = TextEditingController();
final FocusNode _focusNode = FocusNode(); final FocusNode _focusNode = FocusNode();
List<String> _filteredItems = []; List<Tag> _filteredItems = [];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_controller.text = widget.initialValue ?? ''; _controller.text = widget.initialValue?.tag ?? '';
_filteredItems = List.from(widget.items);
_filterItems();
_focusNode.addListener(() { _focusNode.addListener(() {
if (!_focusNode.hasFocus) { if (!_focusNode.hasFocus) {
@ -38,6 +41,12 @@ class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
}); });
} }
void _filterItems() {
setState(() {
_filteredItems = widget.items.where((tag) => tag.product?.uuid == widget.product).toList();
});
}
void _toggleDropdown() { void _toggleDropdown() {
if (_isOpen) { if (_isOpen) {
_closeDropdown(); _closeDropdown();
@ -87,7 +96,7 @@ class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
shrinkWrap: true, shrinkWrap: true,
itemCount: _filteredItems.length, itemCount: _filteredItems.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = _filteredItems[index]; final tag = _filteredItems[index];
return Container( return Container(
decoration: const BoxDecoration( decoration: const BoxDecoration(
@ -99,19 +108,16 @@ class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
), ),
), ),
child: ListTile( child: ListTile(
title: Text(item, title: Text(tag.tag ?? '',
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyMedium .bodyMedium
?.copyWith( ?.copyWith(color: ColorsManager.textPrimaryColor)),
color: ColorsManager
.textPrimaryColor)),
onTap: () { onTap: () {
_controller.text = item; _controller.text = tag.tag ?? '';
widget.onSelected(item); widget.onSelected(tag);
setState(() { setState(() {
_filteredItems _filteredItems.remove(tag);
.remove(item); // Remove selected item
}); });
_closeDropdown(); _closeDropdown();
}, },
@ -150,11 +156,14 @@ class _DialogTextfieldDropdownState extends State<DialogTextfieldDropdown> {
controller: _controller, controller: _controller,
focusNode: _focusNode, focusNode: _focusNode,
onFieldSubmitted: (value) { onFieldSubmitted: (value) {
widget.onSelected(value); final selectedTag = _filteredItems.firstWhere((tag) => tag.tag == value,
orElse: () => Tag(tag: value));
widget.onSelected(selectedTag);
_closeDropdown(); _closeDropdown();
}, },
onTapOutside: (event) { onTapOutside: (event) {
widget.onSelected(_controller.text); widget.onSelected(_filteredItems.firstWhere((tag) => tag.tag == _controller.text,
orElse: () => Tag(tag: _controller.text)));
_closeDropdown(); _closeDropdown();
}, },
style: Theme.of(context).textTheme.bodyMedium, style: Theme.of(context).textTheme.bodyMedium,

View File

@ -101,7 +101,7 @@ class CustomExpansionTileState extends State<CustomExpansionTile> {
widget.children != null && widget.children != null &&
widget.children!.isNotEmpty) widget.children!.isNotEmpty)
Padding( Padding(
padding: const EdgeInsets.only(left: 48.0), // Indented children padding: const EdgeInsets.only(left: 24.0), // Indented children
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: widget.children!, children: widget.children!,

View File

@ -4,11 +4,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:syncrow_web/firebase_options_dev.dart';
import 'package:syncrow_web/firebase_options_prod.dart'; import 'package:syncrow_web/firebase_options_prod.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.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_event.dart'; import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
@ -34,7 +34,6 @@ Future<void> main() async {
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
MyApp({super.key}); MyApp({super.key});
final GoRouter _router = GoRouter( final GoRouter _router = GoRouter(
@ -56,6 +55,9 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider<CreateRoutineBloc>(
create: (context) => CreateRoutineBloc(),
),
BlocProvider( BlocProvider(
create: (context) => HomeBloc()..add(const FetchUserInfo())), create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>( BlocProvider<VisitorPasswordBloc>(

View File

@ -8,6 +8,7 @@ import 'package:syncrow_web/firebase_options_dev.dart';
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.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_event.dart'; import 'package:syncrow_web/pages/home/bloc/home_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
@ -55,6 +56,9 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider<CreateRoutineBloc>(
create: (context) => CreateRoutineBloc(),
),
BlocProvider( BlocProvider(
create: (context) => HomeBloc()..add(const FetchUserInfo())), create: (context) => HomeBloc()..add(const FetchUserInfo())),
BlocProvider<VisitorPasswordBloc>( BlocProvider<VisitorPasswordBloc>(

View File

@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
import 'package:syncrow_web/pages/access_management/bloc/access_state.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_state.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.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/common/buttons/search_reset_buttons.dart'; import 'package:syncrow_web/pages/common/buttons/search_reset_buttons.dart';
import 'package:syncrow_web/pages/common/custom_table.dart'; import 'package:syncrow_web/pages/common/custom_table.dart';

View File

@ -15,7 +15,6 @@ import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/services/auth_api.dart'; import 'package:syncrow_web/services/auth_api.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart'; import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart'; import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
import 'package:syncrow_web/utils/snack_bar.dart'; import 'package:syncrow_web/utils/snack_bar.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> { class AuthBloc extends Bloc<AuthEvent, AuthState> {

View File

@ -5,9 +5,6 @@ import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/services/devices_mang_api.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
part 'device_managment_event.dart'; part 'device_managment_event.dart';
part 'device_managment_state.dart'; part 'device_managment_state.dart';
@ -46,8 +43,7 @@ class DeviceManagementBloc
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (spaceBloc.state.selectedCommunities.isEmpty) { if (spaceBloc.state.selectedCommunities.isEmpty) {
devices = await DevicesManagementApi() devices = await DevicesManagementApi().fetchDevices('', '', projectUuid);
.fetchDevices('', '', projectUuid );
} else { } else {
for (var community in spaceBloc.state.selectedCommunities) { for (var community in spaceBloc.state.selectedCommunities) {
List<String> spacesList = List<String> spacesList =

View File

@ -244,6 +244,7 @@ SOS
SwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''), SwitchFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
ModeFunction(deviceId: uuid ?? '', deviceName: name ?? ''), ModeFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
TempSetFunction(deviceId: uuid ?? '', deviceName: name ?? ''), TempSetFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
CurrentTempFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
LevelFunction(deviceId: uuid ?? '', deviceName: name ?? ''), LevelFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
ChildLockFunction(deviceId: uuid ?? '', deviceName: name ?? ''), ChildLockFunction(deviceId: uuid ?? '', deviceName: name ?? ''),
]; ];

View File

@ -1,9 +1,10 @@
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/bloc/project_manager.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_event.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart'; import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart';
import 'package:syncrow_web/pages/routines/view/routines_view.dart'; import 'package:syncrow_web/pages/routines/view/routines_view.dart';
@ -40,10 +41,15 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
backgroundColor: null, backgroundColor: null,
), ),
onPressed: () { onPressed: () {
BlocProvider.of<CreateRoutineBloc>(context)
.add(const ResetSelectedEvent());
context context
.read<RoutineBloc>() .read<RoutineBloc>()
.add(const TriggerSwitchTabsEvent(isRoutineTab: false)); .add(const TriggerSwitchTabsEvent(isRoutineTab: false));
context.read<DeviceManagementBloc>().add(FetchDevices(context)); context
.read<DeviceManagementBloc>()
.add(FetchDevices(context));
}, },
child: Text( child: Text(
'Devices', 'Devices',
@ -61,6 +67,9 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
backgroundColor: null, backgroundColor: null,
), ),
onPressed: () { onPressed: () {
BlocProvider.of<CreateRoutineBloc>(context)
.add(const ResetSelectedEvent());
context context
.read<RoutineBloc>() .read<RoutineBloc>()
.add(const TriggerSwitchTabsEvent(isRoutineTab: true)); .add(const TriggerSwitchTabsEvent(isRoutineTab: true));

View File

@ -29,7 +29,7 @@ class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
final status = final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId); await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = TwoGangStatusModel.fromJson(event.deviceId, status.status); deviceStatus = TwoGangStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(emit); _listenToChanges(event.deviceId);
emit(TwoGangSwitchStatusLoaded(deviceStatus)); emit(TwoGangSwitchStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
emit(TwoGangSwitchError(e.toString())); emit(TwoGangSwitchError(e.toString()));

View File

@ -30,7 +30,7 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
var response = await DevicesManagementApi().getDeviceStatus(deviceId); var response = await DevicesManagementApi().getDeviceStatus(deviceId);
deviceStatus = WallSensorModel.fromJson(response.status); deviceStatus = WallSensorModel.fromJson(response.status);
emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
_listenToChanges(emit); _listenToChanges(emit, deviceId);
} catch (e) { } catch (e) {
emit(WallSensorFailedState(error: e.toString())); emit(WallSensorFailedState(error: e.toString()));
return; return;
@ -52,7 +52,7 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
} }
} }
_listenToChanges(Emitter<WallSensorState> emit) { _listenToChanges(Emitter<WallSensorState> emit, deviceId) {
try { try {
DatabaseReference ref = DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$deviceId'); FirebaseDatabase.instance.ref('device-status/$deviceId');

View File

@ -0,0 +1,39 @@
import 'dart:convert';
class AutomationStatusUpdate {
final String spaceUuid;
final bool isEnable;
AutomationStatusUpdate({
required this.spaceUuid,
required this.isEnable,
});
factory AutomationStatusUpdate.fromRawJson(String str) =>
AutomationStatusUpdate.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory AutomationStatusUpdate.fromJson(Map<String, dynamic> json) =>
AutomationStatusUpdate(
spaceUuid: json["spaceUuid"],
isEnable: json["isEnable"],
);
Map<String, dynamic> toJson() => {
"spaceUuid": spaceUuid,
"isEnable": isEnable,
};
factory AutomationStatusUpdate.fromMap(Map<String, dynamic> map) =>
AutomationStatusUpdate(
spaceUuid: map["spaceUuid"],
isEnable: map["isEnable"],
);
Map<String, dynamic> toMap() => {
"spaceUuid": spaceUuid,
"isEnable": isEnable,
};
}

View File

@ -0,0 +1,51 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_state.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
class CreateRoutineBloc extends Bloc<CreateRoutineEvent, CreateRoutineState> {
CreateRoutineBloc() : super(const CreateRoutineInitial()) {
on<SpaceOnlyWithDevicesEvent>(_fetchSpaceOnlyWithDevices);
on<SaveCommunityIdAndSpaceIdEvent>(saveSpaceIdCommunityId);
on<ResetSelectedEvent>(resetSelected);
}
String selectedSpaceId = '';
String selectedCommunityId = '';
List<SpaceModel> spacesOnlyWithDevices = [];
Future<void> _fetchSpaceOnlyWithDevices(
SpaceOnlyWithDevicesEvent event, Emitter<CreateRoutineState> emit) async {
emit(const SpaceWithDeviceLoadingState());
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
spacesOnlyWithDevices = await CommunitySpaceManagementApi()
.getSpaceOnlyWithDevices(
communityId: event.communityID, projectId: projectUuid);
emit(SpaceWithDeviceLoadedState(spacesOnlyWithDevices));
} catch (e) {
emit(SpaceTreeErrorState('Error loading communities and spaces: $e'));
}
}
saveSpaceIdCommunityId(
SaveCommunityIdAndSpaceIdEvent event, Emitter<CreateRoutineState> emit) {
emit(const SpaceWithDeviceLoadingState());
selectedSpaceId = event.spaceID!;
selectedCommunityId = event.communityID!;
emit(const SelectedState());
}
resetSelected(ResetSelectedEvent event, Emitter<CreateRoutineState> emit) {
emit(const SpaceWithDeviceLoadingState());
selectedSpaceId = '';
selectedCommunityId = '';
emit(const ResetSelectedState());
}
}

View File

@ -0,0 +1,43 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
abstract class CreateRoutineEvent extends Equatable {
const CreateRoutineEvent();
@override
List<Object?> get props => [];
}
class AddToIfContainer extends CreateRoutineEvent {
final SpaceModel spaceModel;
const AddToIfContainer(this.spaceModel);
@override
List<Object?> get props => [spaceModel];
}
class SpaceOnlyWithDevicesEvent extends CreateRoutineEvent {
final String communityID;
const SpaceOnlyWithDevicesEvent(this.communityID);
@override
List<Object> get props => [communityID];
}
class SaveCommunityIdAndSpaceIdEvent extends CreateRoutineEvent {
final String? communityID;
final String? spaceID;
const SaveCommunityIdAndSpaceIdEvent({this.communityID, this.spaceID});
@override
List<Object> get props => [communityID!, spaceID!];
}
class ResetSelectedEvent extends CreateRoutineEvent {
const ResetSelectedEvent();
@override
List<Object> get props => [];
}

View File

@ -0,0 +1,46 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
abstract class CreateRoutineState extends Equatable {
const CreateRoutineState();
@override
List<Object?> get props => [];
}
class CreateRoutineInitial extends CreateRoutineState {
const CreateRoutineInitial();
}
class SpaceWithDeviceLoadingState extends CreateRoutineState {
const SpaceWithDeviceLoadingState();
}
class SpaceWithDeviceLoadedState extends CreateRoutineState {
final List<SpaceModel> spaces;
const SpaceWithDeviceLoadedState(this.spaces);
@override
List<Object?> get props => [spaces];
}
class SpaceTreeErrorState extends CreateRoutineState {
final String errorMessage;
const SpaceTreeErrorState(this.errorMessage);
@override
List<Object?> get props => [errorMessage];
}
class SelectedState extends CreateRoutineState {
const SelectedState();
}
class ResetSelectedState extends CreateRoutineState {
const ResetSelectedState();
}

View File

@ -6,6 +6,8 @@ import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart'; import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart';
import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_scene_model.dart'; import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_scene_model.dart';
import 'package:syncrow_web/pages/routines/models/delay/delay_fucntions.dart'; import 'package:syncrow_web/pages/routines/models/delay/delay_fucntions.dart';
@ -16,9 +18,6 @@ import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/services/devices_mang_api.dart';
import 'package:syncrow_web/services/routines_api.dart'; import 'package:syncrow_web/services/routines_api.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/constants/temp_const.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
import 'package:syncrow_web/utils/navigation_service.dart'; import 'package:syncrow_web/utils/navigation_service.dart';
import 'package:syncrow_web/utils/snack_bar.dart'; import 'package:syncrow_web/utils/snack_bar.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
@ -55,7 +54,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
on<TriggerSwitchTabsEvent>(_triggerSwitchTabsEvent); on<TriggerSwitchTabsEvent>(_triggerSwitchTabsEvent);
on<CreateNewRoutineViewEvent>(_createNewRoutineViewEvent); on<CreateNewRoutineViewEvent>(_createNewRoutineViewEvent);
on<ResetErrorMessage>(_resetErrorMessage); on<ResetErrorMessage>(_resetErrorMessage);
on<SceneTrigger>(_onSceneTrigger);
on<UpdateAutomationStatus>(_onUpdateAutomationStatus);
} }
String selectedSpaceId = '';
String selectedCommunityId = '';
FutureOr<void> _triggerSwitchTabsEvent( FutureOr<void> _triggerSwitchTabsEvent(
TriggerSwitchTabsEvent event, TriggerSwitchTabsEvent event,
@ -174,17 +177,25 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
emit(state.copyWith(isLoading: true, errorMessage: null)); emit(state.copyWith(isLoading: true, errorMessage: null));
List<ScenesModel> scenes = []; List<ScenesModel> scenes = [];
try { try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
BuildContext context = NavigationService.navigatorKey.currentContext!; BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>(); var createRoutineBloc = context.read<CreateRoutineBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) { final projectUuid = await ProjectManager.getProjectUUID() ?? '';
List<String> spacesList = if (createRoutineBloc.selectedSpaceId == '' &&
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; createRoutineBloc.selectedCommunityId == '') {
for (var spaceId in spacesList) { var spaceBloc = context.read<SpaceTreeBloc>();
scenes.addAll( for (var communityId in spaceBloc.state.selectedCommunities) {
await SceneApi.getScenes(spaceId, communityId, projectUuid)); List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) {
scenes.addAll(
await SceneApi.getScenes(spaceId, communityId, projectUuid));
}
} }
} else {
scenes.addAll(await SceneApi.getScenes(
createRoutineBloc.selectedSpaceId,
createRoutineBloc.selectedCommunityId,
projectUuid));
} }
emit(state.copyWith( emit(state.copyWith(
@ -205,16 +216,28 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
LoadAutomation event, Emitter<RoutineState> emit) async { LoadAutomation event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null)); emit(state.copyWith(isLoading: true, errorMessage: null));
List<ScenesModel> automations = []; List<ScenesModel> automations = [];
final projectId = await ProjectManager.getProjectUUID() ?? '';
BuildContext context = NavigationService.navigatorKey.currentContext!;
var createRoutineBloc = context.read<CreateRoutineBloc>();
try { try {
BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>(); if (createRoutineBloc.selectedSpaceId == '' &&
for (var communityId in spaceBloc.state.selectedCommunities) { createRoutineBloc.selectedCommunityId == '') {
List<String> spacesList = var spaceBloc = context.read<SpaceTreeBloc>();
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; for (var communityId in spaceBloc.state.selectedCommunities) {
for (var spaceId in spacesList) { List<String> spacesList =
automations.addAll(await SceneApi.getAutomation(spaceId)); spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) {
automations.addAll(
await SceneApi.getAutomation(spaceId, communityId, projectId));
}
} }
} else {
automations.addAll(await SceneApi.getAutomation(
createRoutineBloc.selectedSpaceId,
createRoutineBloc.selectedCommunityId,
projectId));
} }
emit(state.copyWith( emit(state.copyWith(
automations: automations, automations: automations,
@ -314,10 +337,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}).toList(); }).toList();
BuildContext context = NavigationService.navigatorKey.currentContext!; BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>(); var createRoutineBloc = context.read<CreateRoutineBloc>();
final createSceneModel = CreateSceneModel( final createSceneModel = CreateSceneModel(
spaceUuid: spaceBloc.state.selectedSpaces[0], spaceUuid: createRoutineBloc.selectedSpaceId,
iconId: state.selectedIcon ?? '', iconId: state.selectedIcon ?? '',
showInDevice: true, showInDevice: true,
sceneName: state.routineName ?? '', sceneName: state.routineName ?? '',
@ -347,6 +370,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
Future<void> _onCreateAutomation( Future<void> _onCreateAutomation(
CreateAutomationEvent event, Emitter<RoutineState> emit) async { CreateAutomationEvent event, Emitter<RoutineState> emit) async {
try { try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (state.routineName == null || state.routineName!.isEmpty) { if (state.routineName == null || state.routineName!.isEmpty) {
emit(state.copyWith( emit(state.copyWith(
errorMessage: 'Automation name is required', errorMessage: 'Automation name is required',
@ -443,10 +467,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}); });
}).toList(); }).toList();
BuildContext context = NavigationService.navigatorKey.currentContext!; BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>(); var createRoutineBloc = context.read<CreateRoutineBloc>();
final createAutomationModel = CreateAutomationModel( final createAutomationModel = CreateAutomationModel(
spaceUuid: spaceBloc.state.selectedSpaces[0], spaceUuid: createRoutineBloc.selectedSpaceId,
automationName: state.routineName ?? '', automationName: state.routineName ?? '',
decisionExpr: state.selectedAutomationOperator, decisionExpr: state.selectedAutomationOperator,
effectiveTime: EffectiveTime( effectiveTime: EffectiveTime(
@ -458,7 +482,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
actions: actions, actions: actions,
); );
final result = await SceneApi.createAutomation(createAutomationModel); final result =
await SceneApi.createAutomation(createAutomationModel, projectUuid);
if (result['success']) { if (result['success']) {
add(ResetRoutineState()); add(ResetRoutineState());
add(const LoadAutomation()); add(const LoadAutomation());
@ -836,22 +861,33 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
isUpdate: false, isUpdate: false,
createRoutineView: false)); createRoutineView: false));
} }
FutureOr<void> _deleteScene(DeleteScene event, Emitter<RoutineState> emit) async {
FutureOr<void> _deleteScene(
DeleteScene event, Emitter<RoutineState> emit) async {
try { try {
final projectId = await ProjectManager.getProjectUUID() ?? '';
emit(state.copyWith(isLoading: true)); emit(state.copyWith(isLoading: true));
BuildContext context = NavigationService.navigatorKey.currentContext!; BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>(); var spaceBloc = context.read<SpaceTreeBloc>();
if (state.isTabToRun) { if (state.isTabToRun) {
await SceneApi.deleteScene( await SceneApi.deleteScene(
unitUuid: spaceBloc.state.selectedSpaces[0], unitUuid: spaceBloc.state.selectedSpaces[0], sceneId: state.sceneId ?? '');
sceneId: state.sceneId ?? '');
} else { } else {
await SceneApi.deleteAutomation( await SceneApi.deleteAutomation(
unitUuid: spaceBloc.state.selectedSpaces[0], unitUuid: spaceBloc.state.selectedSpaces[0],
automationId: state.automationId ?? ''); automationId: state.automationId ?? '',
projectId: projectId);
} }
// var createRoutineBloc = context.read<CreateRoutineBloc>();
// if (state.isTabToRun) {
// await SceneApi.deleteScene(
// unitUuid: createRoutineBloc.selectedSpaceId,
// sceneId: state.sceneId ?? '');
// } else {
// await SceneApi.deleteAutomation(
// projectId: projectId,
// unitUuid: createRoutineBloc.selectedSpaceId,
// automationId: state.automationId ?? '');
// }
add(const LoadScenes()); add(const LoadScenes());
add(const LoadAutomation()); add(const LoadAutomation());
@ -864,7 +900,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
)); ));
} }
} }
// FutureOr<void> _deleteAutomation(DeleteAutomation event, Emitter<RoutineState> emit) { // FutureOr<void> _deleteAutomation(DeleteAutomation event, Emitter<RoutineState> emit) {
// try { // try {
// emit(state.copyWith(isLoading: true)); // emit(state.copyWith(isLoading: true));
@ -884,18 +920,26 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
emit(state.copyWith(isLoading: true)); emit(state.copyWith(isLoading: true));
try { try {
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
List<AllDevicesModel> devices = []; List<AllDevicesModel> devices = [];
BuildContext context = NavigationService.navigatorKey.currentContext!; BuildContext context = NavigationService.navigatorKey.currentContext!;
var createRoutineBloc = context.read<CreateRoutineBloc>();
var spaceBloc = context.read<SpaceTreeBloc>(); var spaceBloc = context.read<SpaceTreeBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList = if (createRoutineBloc.selectedSpaceId == '' &&
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; createRoutineBloc.selectedCommunityId == '') {
for (var spaceId in spacesList) { for (var communityId in spaceBloc.state.selectedCommunities) {
devices.addAll(await DevicesManagementApi() List<String> spacesList =
.fetchDevices(communityId, spaceId, projectUuid)); spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) {
devices.addAll(await DevicesManagementApi()
.fetchDevices(communityId, spaceId, projectUuid));
}
} }
} else {
devices.addAll(await DevicesManagementApi().fetchDevices(
createRoutineBloc.selectedCommunityId,
createRoutineBloc.selectedSpaceId,
projectUuid));
} }
emit(state.copyWith(isLoading: false, devices: devices)); emit(state.copyWith(isLoading: false, devices: devices));
@ -1090,10 +1134,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}).toList(); }).toList();
BuildContext context = NavigationService.navigatorKey.currentContext!; BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>(); var spaceBloc = context.read<CreateRoutineBloc>();
final createAutomationModel = CreateAutomationModel( final createAutomationModel = CreateAutomationModel(
spaceUuid: spaceBloc.state.selectedSpaces[0], spaceUuid: spaceBloc.selectedSpaceId,
automationName: state.routineName ?? '', automationName: state.routineName ?? '',
decisionExpr: state.selectedAutomationOperator, decisionExpr: state.selectedAutomationOperator,
effectiveTime: EffectiveTime( effectiveTime: EffectiveTime(
@ -1104,9 +1148,9 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
conditions: conditions, conditions: conditions,
actions: actions, actions: actions,
); );
final projectId = await ProjectManager.getProjectUUID() ?? '';
final result = await SceneApi.updateAutomation( final result = await SceneApi.updateAutomation(
createAutomationModel, state.automationId ?? ''); createAutomationModel, state.automationId ?? '', projectId);
if (result['success']) { if (result['success']) {
add(ResetRoutineState()); add(ResetRoutineState());
@ -1129,6 +1173,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
Future<void> _onGetAutomationDetails( Future<void> _onGetAutomationDetails(
GetAutomationDetails event, Emitter<RoutineState> emit) async { GetAutomationDetails event, Emitter<RoutineState> emit) async {
try { try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
emit(state.copyWith( emit(state.copyWith(
isLoading: true, isLoading: true,
isUpdate: true, isUpdate: true,
@ -1140,7 +1185,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
)); ));
final automationDetails = final automationDetails =
await SceneApi.getAutomationDetails(event.automationId); await SceneApi.getAutomationDetails(event.automationId, projectUuid);
final Map<String, Map<String, dynamic>> deviceIfCards = {}; final Map<String, Map<String, dynamic>> deviceIfCards = {};
final Map<String, Map<String, dynamic>> deviceThenCards = {}; final Map<String, Map<String, dynamic>> deviceThenCards = {};
@ -1318,4 +1363,77 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
)); ));
} }
} }
Future<void> _onSceneTrigger(
SceneTrigger event, Emitter<RoutineState> emit) async {
emit(state.copyWith(loadingSceneId: event.sceneId));
try {
final success = await SceneApi.triggerScene(event.sceneId!);
if (success) {
emit(state.copyWith(
loadingSceneId: null,
// Add success state if needed
));
// Optional: Add delay to show success feedback
await Future.delayed(const Duration(milliseconds: 500));
} else {
emit(state.copyWith(
loadingSceneId: null,
errorMessage: 'Trigger failed',
));
}
} catch (e) {
emit(state.copyWith(
loadingSceneId: null,
errorMessage: 'Trigger error: ${e.toString()}',
));
}
}
Future<void> _onUpdateAutomationStatus(
UpdateAutomationStatus event, Emitter<RoutineState> emit) async {
// Create a new set safely
final currentLoadingIds = state.loadingAutomationIds;
final newLoadingIds = {...currentLoadingIds!}..add(event.automationId);
emit(state.copyWith(loadingAutomationIds: newLoadingIds));
try {
final projectId = await ProjectManager.getProjectUUID() ?? '';
final success = await SceneApi.updateAutomationStatus(
event.automationId, event.automationStatusUpdate, projectId);
if (success) {
final updatedAutomations = await SceneApi.getAutomationByUnitId(
event.automationStatusUpdate.spaceUuid,
event.communityId,
projectId);
// Remove from loading set safely
final updatedLoadingIds = {...state.loadingAutomationIds!}
..remove(event.automationId);
emit(state.copyWith(
automations: updatedAutomations,
loadingAutomationIds: updatedLoadingIds,
));
} else {
final updatedLoadingIds = {...state.loadingAutomationIds!}
..remove(event.automationId);
emit(state.copyWith(
loadingAutomationIds: updatedLoadingIds,
errorMessage: 'Update failed',
));
}
} catch (e) {
final updatedLoadingIds = {...state.loadingAutomationIds!}
..remove(event.automationId);
emit(state.copyWith(
loadingAutomationIds: updatedLoadingIds,
errorMessage: 'Update error: ${e.toString()}',
));
}
}
} }

View File

@ -210,3 +210,28 @@ class ResetRoutineState extends RoutineEvent {}
class ClearFunctions extends RoutineEvent {} class ClearFunctions extends RoutineEvent {}
class ResetErrorMessage extends RoutineEvent {} class ResetErrorMessage extends RoutineEvent {}
class SceneTrigger extends RoutineEvent {
final String? sceneId;
final String? name;
const SceneTrigger({this.sceneId, this.name});
@override
List<Object> get props => [sceneId!,name!];
}
//updateAutomationStatus
class UpdateAutomationStatus extends RoutineEvent {
final String automationId;
final AutomationStatusUpdate automationStatusUpdate;
final String communityId;
const UpdateAutomationStatus({required this.automationStatusUpdate, required this.automationId, required this.communityId});
@override
List<Object> get props => [automationStatusUpdate];
}

View File

@ -1,6 +1,7 @@
part of 'routine_bloc.dart'; part of 'routine_bloc.dart';
class RoutineState extends Equatable { class RoutineState extends Equatable {
final String? loadingSceneId;
final List<Map<String, dynamic>> ifItems; final List<Map<String, dynamic>> ifItems;
final List<Map<String, dynamic>> thenItems; final List<Map<String, dynamic>> thenItems;
final List<Map<String, String>> availableCards; final List<Map<String, String>> availableCards;
@ -25,6 +26,7 @@ class RoutineState extends Equatable {
// final String? automationActionExecutor; // final String? automationActionExecutor;
final bool routineTab; final bool routineTab;
final bool createRoutineView; final bool createRoutineView;
final Set<String>? loadingAutomationIds; // Track loading automations
const RoutineState( const RoutineState(
{this.ifItems = const [], {this.ifItems = const [],
@ -47,12 +49,16 @@ class RoutineState extends Equatable {
this.sceneId, this.sceneId,
this.automationId, this.automationId,
this.isUpdate, this.isUpdate,
this.loadingAutomationIds = const <String>{}, // Initialize with empty set
this.loadingSceneId,
this.devices = const [], this.devices = const [],
// this.automationActionExecutor, // this.automationActionExecutor,
this.routineTab = false, this.routineTab = false,
this.createRoutineView = false}); this.createRoutineView = false});
RoutineState copyWith({ RoutineState copyWith({
String? loadingSceneId,
Set<String>? loadingAutomationIds,
List<Map<String, dynamic>>? ifItems, List<Map<String, dynamic>>? ifItems,
List<Map<String, dynamic>>? thenItems, List<Map<String, dynamic>>? thenItems,
List<ScenesModel>? scenes, List<ScenesModel>? scenes,
@ -79,6 +85,8 @@ class RoutineState extends Equatable {
bool? createRoutineView, bool? createRoutineView,
}) { }) {
return RoutineState( return RoutineState(
loadingSceneId: loadingSceneId,
loadingAutomationIds: loadingAutomationIds ?? this.loadingAutomationIds,
ifItems: ifItems ?? this.ifItems, ifItems: ifItems ?? this.ifItems,
thenItems: thenItems ?? this.thenItems, thenItems: thenItems ?? this.thenItems,
scenes: scenes ?? this.scenes, scenes: scenes ?? this.scenes,
@ -109,6 +117,7 @@ class RoutineState extends Equatable {
@override @override
List<Object?> get props => [ List<Object?> get props => [
loadingAutomationIds,
ifItems, ifItems,
thenItems, thenItems,
scenes, scenes,
@ -134,3 +143,38 @@ class RoutineState extends Equatable {
createRoutineView createRoutineView
]; ];
} }
class SceneInitial extends RoutineState {}
class SceneLoading extends RoutineState {}
class SceneLoaded extends RoutineState {
final List<ScenesModel>? scenesOrAutomation;
const SceneLoaded({this.scenesOrAutomation});
@override
List<Object?> get props => [
scenesOrAutomation,
];
}
class SceneError extends RoutineState {
final String message;
const SceneError({required this.message});
@override
List<Object> get props => [message];
}
class SceneTriggerSuccess extends RoutineState {
final String sceneName;
const SceneTriggerSuccess(this.sceneName);
@override
List<Object> get props => [sceneName];
}
class UpdateAutomationStatusLoading extends RoutineState {
const UpdateAutomationStatusLoading();
}

View File

@ -0,0 +1,96 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_state.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/style.dart';
class CommunityDropdown extends StatelessWidget {
final String? selectedValue;
final Function(String?) onChanged;
const CommunityDropdown({
Key? key,
required this.selectedValue,
required this.onChanged,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Community",
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w400,
fontSize: 13,
color: ColorsManager.blackColor,
),
),
const SizedBox(height: 8),
BlocBuilder<SpaceTreeBloc, SpaceTreeState>(
builder: (context, state) {
List<CommunityModel> communities = state.isSearching
? state.filteredCommunity
: state.communityList;
return SizedBox(
child: DropdownButtonFormField<String>(
dropdownColor: ColorsManager.whiteColors,
value: selectedValue,
items: communities.map((community) {
return DropdownMenuItem<String>(
value: community.uuid,
child: Text(' ${community.name}'),
);
}).toList(),
onChanged: onChanged,
icon: const SizedBox.shrink(),
borderRadius: const BorderRadius.all(Radius.circular(10)),
hint: Padding(
padding: EdgeInsets.only(left: 10),
child: Text(
"Please Select",
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.textGray,
),
),
),
decoration: inputTextFormDeco().copyWith(
contentPadding: EdgeInsets.zero,
suffixIcon: Container(
padding: EdgeInsets.zero,
width: 70,
height: 45,
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: const BorderRadius.only(
bottomRight: Radius.circular(10),
topRight: Radius.circular(10),
),
border: Border.all(
color: ColorsManager.textGray,
width: 1.0,
),
),
child: const Center(
child: Icon(
Icons.keyboard_arrow_down,
color: ColorsManager.textGray,
),
),
),
),
),
);
},
),
],
),
);
}
}

View File

@ -0,0 +1,148 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_state.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/create_new_routines/commu_dropdown.dart';
import 'package:syncrow_web/pages/routines/create_new_routines/space_dropdown.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class CreateNewRoutinesDialog extends StatefulWidget {
const CreateNewRoutinesDialog({Key? key}) : super(key: key);
@override
State<CreateNewRoutinesDialog> createState() =>
_CreateNewRoutinesDialogState();
}
class _CreateNewRoutinesDialogState extends State<CreateNewRoutinesDialog> {
String? _selectedCommunity;
String? _selectedSpace;
void _fetchSpaces(String communityId) {
context
.read<CreateRoutineBloc>()
.add(SpaceOnlyWithDevicesEvent(communityId));
}
@override
Widget build(BuildContext context) {
return BlocBuilder<CreateRoutineBloc, CreateRoutineState>(
builder: (context, state) {
final _bloc = BlocProvider.of<CreateRoutineBloc>(context);
final spaces = _bloc.spacesOnlyWithDevices;
final isLoading = state is SpaceWithDeviceLoadingState;
String spaceHint = 'Select a community first';
if (_selectedCommunity != null) {
if (isLoading) {
spaceHint = 'Loading spaces...';
} else if (spaces.isEmpty) {
spaceHint = 'No spaces available';
} else {
spaceHint = 'Select Space';
}
}
return AlertDialog(
backgroundColor: Colors.white,
insetPadding: EdgeInsets.zero,
contentPadding: EdgeInsets.zero,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
title: Text(
'Create New Routines',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: ColorsManager.primaryColor,
),
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Divider(),
CommunityDropdown(
selectedValue: _selectedCommunity,
onChanged: (String? newValue) {
setState(() {
_selectedCommunity = newValue;
_selectedSpace = null;
});
if (newValue != null) {
_fetchSpaces(newValue);
}
},
),
const SizedBox(height: 16),
SpaceDropdown(
hintMessage: spaceHint,
spaces: spaces,
selectedValue: _selectedSpace,
onChanged: (String? newValue) {
setState(() {
_selectedSpace = newValue;
});
},
),
const Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Padding(
padding: const EdgeInsets.only(
left: 20,
right: 20,
),
child: TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(
'Cancel',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
color: ColorsManager.blackColor,
),
),
),
),
Padding(
padding: const EdgeInsets.only(
left: 20,
right: 20,
),
child: TextButton(
onPressed:
_selectedCommunity != null && _selectedSpace != null
? () {
Navigator.of(context).pop({
'community': _selectedCommunity,
'space': _selectedSpace,
});
}
: null,
child: Text(
'Next',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
color: _selectedCommunity != null &&
_selectedSpace != null
? ColorsManager.blueColor
: Colors.blue.shade100,
),
),
),
),
],
),
],
),
);
},
);
}
}

View File

@ -0,0 +1,100 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/style.dart';
class SpaceDropdown extends StatelessWidget {
final List<SpaceModel> spaces;
final String? selectedValue;
final Function(String?)? onChanged;
final String hintMessage;
const SpaceDropdown({
Key? key,
required this.spaces,
required this.selectedValue,
required this.onChanged,
required this.hintMessage,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Space",
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontWeight: FontWeight.w400,
fontSize: 13,
color: ColorsManager.blackColor,
),
),
const SizedBox(height: 8),
DropdownButtonFormField<String>(
value: selectedValue,
items: spaces.map((space) {
return DropdownMenuItem<String>(
value: space.uuid,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
' ${space.name}',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12,
color: ColorsManager.blackColor,
),
),
Text(
' ${space.lastThreeParents}',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 12,
),
),
],
));
}).toList(),
onChanged: onChanged,
icon: const SizedBox.shrink(),
borderRadius: const BorderRadius.all(Radius.circular(10)),
hint: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
hintMessage,
style: Theme.of(context).textTheme.bodySmall!.copyWith(
color: ColorsManager.textGray,
),
),
),
decoration: inputTextFormDeco().copyWith(
contentPadding: EdgeInsets.zero,
suffixIcon: Container(
width: 70,
height: 45,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: const BorderRadius.only(
bottomRight: Radius.circular(10),
topRight: Radius.circular(10),
),
border: Border.all(
color: ColorsManager.textGray,
width: 1.0,
),
),
child: const Icon(
Icons.keyboard_arrow_down,
color: ColorsManager.textGray,
),
),
),
),
],
),
);
}
}

View File

@ -41,6 +41,11 @@ class DeviceDialogHelper {
final deviceSelectedFunctions = final deviceSelectedFunctions =
routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? []; routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? [];
if (removeComparetors) {
//remove the current temp function in the 'if container'
functions.removeAt(3);
}
switch (productType) { switch (productType) {
case 'AC': case 'AC':
return ACHelper.showACFunctionsDialog(context, functions, data['device'], return ACHelper.showACFunctionsDialog(context, functions, data['device'],

View File

@ -151,3 +151,32 @@ class ChildLockFunction extends ACFunction {
), ),
]; ];
} }
class CurrentTempFunction extends ACFunction {
final int min;
final int max;
final int step;
CurrentTempFunction({required super.deviceId, required super.deviceName})
: min = -100,
max = 990,
step = 1,
super(
code: 'temp_current',
operationName: 'Current Temperature',
icon: Assets.currentTemp,
);
@override
List<ACOperationalValue> getOperationalValues() {
List<ACOperationalValue> values = [];
for (int temp = min; temp <= max; temp += step) {
values.add(ACOperationalValue(
icon: Assets.currentTemp,
description: "${temp / 10}°C",
value: temp,
));
}
return values;
}
}

View File

@ -9,6 +9,9 @@ class ScenesModel {
final String status; final String status;
final String type; final String type;
final String? icon; final String? icon;
final String spaceName;
final String spaceId;
final String communityId;
ScenesModel({ ScenesModel({
required this.id, required this.id,
@ -16,6 +19,9 @@ class ScenesModel {
required this.name, required this.name,
required this.status, required this.status,
required this.type, required this.type,
required this.spaceName,
required this.spaceId,
required this.communityId,
this.icon, this.icon,
}); });
@ -41,6 +47,9 @@ class ScenesModel {
name: json["name"] ?? '', name: json["name"] ?? '',
status: json["status"] ?? '', status: json["status"] ?? '',
type: json["type"] ?? '', type: json["type"] ?? '',
spaceName: json["spaceName"] ?? '',
spaceId: json["spaceId"] ?? '',
communityId: json["communityId"] ?? '',
icon: icon:
isAutomation == true ? Assets.automation : (json["icon"] as String?), isAutomation == true ? Assets.automation : (json["icon"] as String?),
); );
@ -52,5 +61,8 @@ class ScenesModel {
"name": name, "name": name,
"status": status, "status": status,
"type": type, "type": type,
"spaceName": spaceName,
"spaceId": spaceId,
"communityId": communityId,
}; };
} }

View File

@ -1,10 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_event.dart';
import 'package:syncrow_web/pages/routines/bloc/create_routine_bloc/create_routine_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/create_new_routines/create_new_routines.dart';
import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart'; import 'package:syncrow_web/pages/routines/view/create_new_routine_view.dart';
import 'package:syncrow_web/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart'; import 'package:syncrow_web/pages/routines/widgets/main_routine_view/fetch_routine_scenes_automation.dart';
import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_view_card.dart'; import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_view_card.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
@ -16,10 +18,23 @@ class RoutinesView extends StatefulWidget {
} }
class _RoutinesViewState extends State<RoutinesView> { class _RoutinesViewState extends State<RoutinesView> {
@override void _handleRoutineCreation(BuildContext context) async {
void initState() { final result = await showDialog<Map<String, dynamic>>(
super.initState(); context: context,
// context.read<RoutineBloc>().add(FetchDevicesInRoutine()); builder: (context) => const CreateNewRoutinesDialog(),
);
if (result == null) return;
final communityId = result['community'];
final spaceId = result['space'];
final _bloc = BlocProvider.of<CreateRoutineBloc>(context);
final routineBloc = context.read<RoutineBloc>();
_bloc.add(SaveCommunityIdAndSpaceIdEvent(
communityID: communityId, spaceID: spaceId));
await Future.delayed(const Duration(seconds: 1));
routineBloc.add(const CreateNewRoutineViewEvent(createRoutineView: true));
await Future.delayed(const Duration(milliseconds:500));
_bloc.add(const ResetSelectedEvent());
} }
@override @override
@ -29,73 +44,57 @@ class _RoutinesViewState extends State<RoutinesView> {
if (state.createRoutineView) { if (state.createRoutineView) {
return const CreateNewRoutineView(); return const CreateNewRoutineView();
} }
return Row( return Row(
children: [ children: [
Expanded(child: SpaceTreeView( Expanded(
onSelect: () { child: SpaceTreeView(
context.read<RoutineBloc>() onSelect: () => context.read<RoutineBloc>()
..add(const LoadScenes()) ..add(const LoadScenes())
..add(const LoadAutomation()); ..add(const LoadAutomation()),
}, ),
)), ),
Expanded( Expanded(
flex: 4, flex: 4,
child: ListView(children: [ child: ListView(
Container( children: [
padding: const EdgeInsets.all(16), Container(
height: MediaQuery.sizeOf(context).height, padding: const EdgeInsets.all(16),
child: Column( height: MediaQuery.sizeOf(context).height,
mainAxisSize: MainAxisSize.min, child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, children: [
children: [ Text(
Text( "Create New Routines",
"Create New Routines", style:
style: Theme.of(context).textTheme.titleLarge?.copyWith( Theme.of(context).textTheme.titleLarge?.copyWith(
color: ColorsManager.grayColor, color: ColorsManager.grayColor,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
const SizedBox( const SizedBox(height: 10),
height: 10, RoutineViewCard(
), isLoading: false,
RoutineViewCard( onChanged: (v) {},
onTap: () { status: '',
if (context.read<SpaceTreeBloc>().state.selectedCommunities.length == 1 && spaceId: '',
context.read<SpaceTreeBloc>().state.selectedSpaces.length == 1) { automationId: '',
context.read<RoutineBloc>().add( communityId: '',
(ResetRoutineState()), sceneId: '',
); cardType: '',
BlocProvider.of<RoutineBloc>(context).add( spaceName: '',
const CreateNewRoutineViewEvent(createRoutineView: true), onTap: () => _handleRoutineCreation(context),
); icon: Icons.add,
} else { textString: '',
ScaffoldMessenger.of(context).showSnackBar( ),
SnackBar( const SizedBox(height: 15),
content: Text( const Expanded(child: FetchRoutineScenesAutomation()),
context.read<SpaceTreeBloc>().state.selectedSpaces.isEmpty ],
? 'Please select a space' ),
: 'Please select only one space to proceed'),
),
);
// CustomSnackBar.redSnackBar(
// context.read<SpaceTreeBloc>().state.selectedSpaces.isEmpty
// ? 'Please select a space'
// : 'Please select only one space to proceed');
}
},
icon: Icons.add,
textString: '',
),
const SizedBox(
height: 15,
),
const Expanded(child: FetchRoutineScenesAutomation()),
],
), ),
), ],
]), ),
), )
], ],
); );
}, },

View File

@ -1,8 +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:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_view_card.dart'; import 'package:syncrow_web/pages/routines/widgets/main_routine_view/routine_view_card.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -12,7 +12,8 @@ class FetchRoutineScenesAutomation extends StatefulWidget {
const FetchRoutineScenesAutomation({super.key}); const FetchRoutineScenesAutomation({super.key});
@override @override
State<FetchRoutineScenesAutomation> createState() => _FetchRoutineScenesState(); State<FetchRoutineScenesAutomation> createState() =>
_FetchRoutineScenesState();
} }
class _FetchRoutineScenesState extends State<FetchRoutineScenesAutomation> class _FetchRoutineScenesState extends State<FetchRoutineScenesAutomation>
@ -45,46 +46,70 @@ class _FetchRoutineScenesState extends State<FetchRoutineScenesAutomation>
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
if (state.scenes.isEmpty) if (state.scenes.isEmpty)
Text( Expanded(
"No scenes found", child: Text(
style: context.textTheme.bodyMedium?.copyWith( "No scenes found",
color: ColorsManager.grayColor, style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.grayColor,
),
), ),
), ),
if (state.scenes.isNotEmpty) if (state.scenes.isNotEmpty)
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
maxHeight: isSmallScreenSize(context) ? 160 : 170, maxHeight: isSmallScreenSize(context) ? 190 : 200,
maxWidth: MediaQuery.sizeOf(context).width * 0.7), maxWidth: MediaQuery.sizeOf(context).width * 0.8),
child: ListView.builder( child: ListView.builder(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: state.scenes.length, itemCount: state.scenes.length,
itemBuilder: (context, index) => Padding( itemBuilder: (context, index) {
padding: EdgeInsets.only( final scene = state.scenes[index];
right: isSmallScreenSize(context) ? 4.0 : 8.0, final isLoading =
), state.loadingSceneId == scene.id;
child: RoutineViewCard(
onTap: () { return Padding(
BlocProvider.of<RoutineBloc>(context).add( padding: EdgeInsets.only(
const CreateNewRoutineViewEvent(createRoutineView: true), right: isSmallScreenSize(context) ? 4.0 : 8.0,
); ),
context.read<RoutineBloc>().add( child: RoutineViewCard(
GetSceneDetails( isLoading: isLoading,
sceneId: state.scenes[index].id, sceneOnTap: () {
isTabToRun: true, context.read<RoutineBloc>().add(
isUpdate: true, SceneTrigger(
), sceneId: scene.id,
name: scene.name));
},
status: state.scenes[index].status,
communityId:
state.scenes[index].communityId ?? '',
spaceId: state.scenes[index].spaceId,
sceneId: state.scenes[index].sceneTuyaId!,
automationId: state.scenes[index].id,
cardType: 'scenes',
spaceName: state.scenes[index].spaceName,
onTap: () {
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(
createRoutineView: true),
); );
}, context.read<RoutineBloc>().add(
textString: state.scenes[index].name, GetSceneDetails(
icon: state.scenes[index].icon ?? Assets.logoHorizontal, sceneId: state.scenes[index].id,
isFromScenes: true, isTabToRun: true,
iconInBytes: state.scenes[index].iconInBytes, isUpdate: true,
), ),
), );
), },
textString: state.scenes[index].name,
icon: state.scenes[index].icon ??
Assets.logoHorizontal,
isFromScenes: true,
iconInBytes: state.scenes[index].iconInBytes,
),
);
}),
), ),
const SizedBox(height: 15), const SizedBox(height: 10),
Text( Text(
"Automations", "Automations",
style: Theme.of(context).textTheme.titleLarge?.copyWith( style: Theme.of(context).textTheme.titleLarge?.copyWith(
@ -92,43 +117,77 @@ class _FetchRoutineScenesState extends State<FetchRoutineScenesAutomation>
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 5),
if (state.automations.isEmpty) if (state.automations.isEmpty)
Text( Expanded(
"No automations found", child: Text(
style: context.textTheme.bodyMedium?.copyWith( "No automations found",
color: ColorsManager.grayColor, style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.grayColor,
),
), ),
), ),
if (state.automations.isNotEmpty) if (state.automations.isNotEmpty)
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
maxHeight: isSmallScreenSize(context) ? 160 : 170, maxHeight: isSmallScreenSize(context) ? 185 : 192,
maxWidth: MediaQuery.sizeOf(context).width * 0.7), maxWidth: MediaQuery.sizeOf(context).width * 0.7),
child: ListView.builder( child: ListView.builder(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: state.automations.length, itemCount: state.automations.length,
itemBuilder: (context, index) => Padding( itemBuilder: (context, index) {
padding: EdgeInsets.only( final isLoading = state.automations!
right: isSmallScreenSize(context) ? 4.0 : 8.0, .contains(state.automations[index].id);
),
child: RoutineViewCard( return Padding(
onTap: () { padding: EdgeInsets.only(
BlocProvider.of<RoutineBloc>(context).add( right: isSmallScreenSize(context) ? 4.0 : 8.0,
const CreateNewRoutineViewEvent(createRoutineView: true), ),
); child: RoutineViewCard(
context.read<RoutineBloc>().add( isLoading: isLoading,
GetAutomationDetails( onChanged: (v) {
automationId: state.automations[index].id, // BlocProvider.of<RoutineBloc>(context)
isAutomation: true, context.read<RoutineBloc>().add(
isUpdate: true), UpdateAutomationStatus(
automationId:
state.automations[index].id,
automationStatusUpdate:
AutomationStatusUpdate(
spaceUuid: state
.automations[index]
.spaceId,
isEnable: v),
communityId: state
.automations[index].communityId,
),
);
},
status: state.automations[index].status,
communityId: '',
spaceId: state.automations[index].spaceId,
sceneId: '',
automationId: state.automations[index].id,
cardType: 'automations',
spaceName: state.scenes[index].spaceName,
onTap: () {
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(
createRoutineView: true),
); );
}, context.read<RoutineBloc>().add(
textString: state.automations[index].name, GetAutomationDetails(
icon: state.automations[index].icon ?? Assets.automation, automationId:
), state.automations[index].id,
), isAutomation: true,
), isUpdate: true),
);
},
textString: state.automations[index].name,
icon: state.automations[index].icon ??
Assets.automation,
),
);
}),
), ),
], ],
), ),

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
@ -6,37 +8,78 @@ import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout { class RoutineViewCard extends StatefulWidget with HelperResponsiveLayout {
const RoutineViewCard({ const RoutineViewCard({
super.key, super.key,
required this.onTap, required this.onTap,
this.sceneOnTap,
required this.icon, required this.icon,
required this.textString, required this.textString,
required this.spaceName,
required this.cardType,
this.isFromScenes, this.isFromScenes,
this.iconInBytes, this.iconInBytes,
required this.sceneId,
required this.communityId,
required this.spaceId,
required this.automationId,
required this.status,
this.onChanged,
required this.isLoading,
}); });
final Function() onTap; final Function() onTap;
final Function()? sceneOnTap;
final dynamic icon; final dynamic icon;
final String textString; final String textString;
final String spaceName;
final String cardType;
final String sceneId;
final String spaceId;
final String status;
final bool isLoading;
final void Function(bool)? onChanged;
final String automationId;
final String communityId;
final bool? isFromScenes; final bool? isFromScenes;
final Uint8List? iconInBytes; final Uint8List? iconInBytes;
@override
State<RoutineViewCard> createState() => _RoutineViewCardState();
}
class _RoutineViewCardState extends State<RoutineViewCard> {
bool _showTemporaryCheck = false;
void _handleSceneTap() {
if (!_showTemporaryCheck) {
setState(() => _showTemporaryCheck = true);
widget.sceneOnTap?.call();
Timer(const Duration(seconds: 3), () {
if (mounted) setState(() => _showTemporaryCheck = false);
});
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final double cardWidth = isSmallScreenSize(context) // Use widget.<mixinMethod> instead of just <mixinMethod>
final double cardWidth = widget.isSmallScreenSize(context)
? 120 ? 120
: isMediumScreenSize(context) : widget.isMediumScreenSize(context)
? 135 ? 135
: 150; : 150;
final double cardHeight = isSmallScreenSize(context) ? 160 : 170; final double cardHeight = widget.isSmallScreenSize(context) ? 190 : 200;
final double iconSize = isSmallScreenSize(context) final double iconSize = widget.isSmallScreenSize(context)
? 50 ? 70
: isMediumScreenSize(context) : widget.isMediumScreenSize(context)
? 60 ? 80
: 70; : 90;
return ConstrainedBox( return ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
@ -50,69 +93,147 @@ class RoutineViewCard extends StatelessWidget with HelperResponsiveLayout {
), ),
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
child: InkWell( child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
Center( widget.cardType != ''
child: Container( ? Row(
decoration: BoxDecoration( mainAxisAlignment: MainAxisAlignment.end,
color: ColorsManager.graysColor, children: [
borderRadius: BorderRadius.circular(120), if (widget.isFromScenes ?? false)
border: Border.all( InkWell(
color: ColorsManager.greyColor, onTap: _handleSceneTap,
width: 2.0, child: SvgPicture.asset(
), _showTemporaryCheck
), ? Assets.scenesPlayIconCheck
height: iconSize, : Assets.scenesPlayIcon,
width: iconSize,
child: (isFromScenes ?? false)
? (iconInBytes != null && iconInBytes?.isNotEmpty == true)
? Image.memory(
iconInBytes!,
height: iconSize,
width: iconSize,
fit: BoxFit.contain, fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) => Image.asset(
Assets.logo,
height: iconSize,
width: iconSize,
fit: BoxFit.contain,
),
)
: Image.asset(
Assets.logo,
height: iconSize,
width: iconSize,
fit: BoxFit.contain,
)
: (icon is String && icon.endsWith('.svg'))
? SvgPicture.asset(
icon,
fit: BoxFit.contain,
)
: Icon(
icon,
color: ColorsManager.dialogBlueTitle,
size: isSmallScreenSize(context) ? 30 : 40,
), ),
), )
), else if (widget.isLoading)
const SizedBox(height: 8), const SizedBox(
Padding( width: 49,
padding: const EdgeInsets.symmetric(horizontal: 3), height: 20,
child: Text( child: Center(
textString, child: SizedBox(
textAlign: TextAlign.center, width: 16,
overflow: TextOverflow.ellipsis, height: 16,
maxLines: 2, child:
style: context.textTheme.bodySmall?.copyWith( CircularProgressIndicator(strokeWidth: 2),
color: ColorsManager.blackColor, ),
fontSize: isSmallScreenSize(context) ? 10 : 12, ),
), )
else
CupertinoSwitch(
activeColor: ColorsManager.primaryColor,
value: widget.status == 'enable',
onChanged: widget.onChanged,
)
],
)
: const SizedBox(),
InkWell(
onTap: widget.onTap,
child: Column(
children: [
Center(
child: Container(
decoration: BoxDecoration(
color: ColorsManager.graysColor,
borderRadius: BorderRadius.circular(120),
border: Border.all(
color: ColorsManager.greyColor,
width: 2.0,
),
),
height: iconSize,
width: iconSize,
child: (widget.isFromScenes ?? false)
? (widget.iconInBytes != null &&
widget.iconInBytes?.isNotEmpty == true)
? Image.memory(
widget.iconInBytes!,
height: iconSize,
width: iconSize,
fit: BoxFit.contain,
errorBuilder:
(context, error, stackTrace) =>
Image.asset(
Assets.logo,
height: iconSize,
width: iconSize,
fit: BoxFit.contain,
),
)
: Image.asset(
Assets.logo,
height: iconSize,
width: iconSize,
fit: BoxFit.contain,
)
: (widget.icon is String &&
widget.icon.endsWith('.svg'))
? SvgPicture.asset(
widget.icon,
fit: BoxFit.contain,
)
: Icon(
widget.icon,
color: ColorsManager.dialogBlueTitle,
size: widget.isSmallScreenSize(context)
? 30
: 40,
),
),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 3),
child: Column(
children: [
Text(
widget.textString,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor,
fontSize:
widget.isSmallScreenSize(context) ? 10 : 12,
),
),
if (widget.spaceName != '')
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
Assets.spaceLocationIcon,
fit: BoxFit.contain,
),
Text(
widget.spaceName,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 2,
style:
context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor,
fontSize:
widget.isSmallScreenSize(context)
? 10
: 12,
),
),
],
),
],
),
),
],
), ),
), ),
], ],

View File

@ -329,8 +329,8 @@ class ACHelper {
) { ) {
return Slider( return Slider(
value: initialValue is int ? initialValue.toDouble() : 200.0, value: initialValue is int ? initialValue.toDouble() : 200.0,
min: 200, min: selectCode == 'temp_current' ? -100 : 200,
max: 300, max: selectCode == 'temp_current' ? 900 : 300,
divisions: 10, divisions: 10,
label: '${((initialValue ?? 160) / 10).toInt()}°C', label: '${((initialValue ?? 160) / 10).toInt()}°C',
onChanged: (value) { onChanged: (value) {

View File

@ -83,7 +83,7 @@ class CustomExpansionTileSpaceTree extends StatelessWidget {
), ),
if (isExpanded && children != null && children!.isNotEmpty) if (isExpanded && children != null && children!.isNotEmpty)
Padding( Padding(
padding: const EdgeInsets.only(left: 48.0), padding: const EdgeInsets.only(left: 24.0),
child: Column( child: Column(
children: children ?? [], children: children ?? [],
), ),

View File

@ -33,14 +33,13 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>( return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
builder: (context, state) { List<CommunityModel> list = state.isSearching ? state.filteredCommunity : state.communityList;
List<CommunityModel> list =
state.isSearching ? state.filteredCommunity : state.communityList;
return Container( return Container(
height: MediaQuery.sizeOf(context).height, height: MediaQuery.sizeOf(context).height,
decoration: decoration: widget.isSide == true
widget.isSide == true ? subSectionContainerDecoration : null, ? subSectionContainerDecoration.copyWith(color: ColorsManager.whiteColors)
: const BoxDecoration(color: ColorsManager.whiteColors),
child: state is SpaceTreeLoadingState child: state is SpaceTreeLoadingState
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: Column( : Column(
@ -50,8 +49,7 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: ColorsManager.circleRolesBackground, color: ColorsManager.circleRolesBackground,
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topRight: Radius.circular(20), topRight: Radius.circular(20), topLeft: Radius.circular(20)),
topLeft: Radius.circular(20)),
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
@ -60,35 +58,27 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
Expanded( Expanded(
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: const BorderRadius.all( borderRadius: const BorderRadius.all(Radius.circular(20)),
Radius.circular(20)), border: Border.all(color: ColorsManager.grayBorder)),
border: Border.all(
color: ColorsManager.grayBorder)),
child: TextFormField( child: TextFormField(
style: style: const TextStyle(color: Colors.black),
const TextStyle(color: Colors.black),
onChanged: (value) { onChanged: (value) {
context context.read<SpaceTreeBloc>().add(SearchQueryEvent(value));
.read<SpaceTreeBloc>()
.add(SearchQueryEvent(value));
}, },
decoration: textBoxDecoration(radios: 20)! decoration: textBoxDecoration(radios: 20)!.copyWith(
.copyWith(
fillColor: Colors.white, fillColor: Colors.white,
suffixIcon: Padding( suffixIcon: Padding(
padding: padding: const EdgeInsets.only(right: 16),
const EdgeInsets.only(right: 16),
child: SvgPicture.asset( child: SvgPicture.asset(
Assets.textFieldSearch, Assets.textFieldSearch,
width: 24, width: 24,
height: 24, height: 24,
), ),
), ),
hintStyle: context.textTheme.bodyMedium hintStyle: context.textTheme.bodyMedium?.copyWith(
?.copyWith( fontWeight: FontWeight.w400,
fontWeight: FontWeight.w400, fontSize: 12,
fontSize: 12, color: ColorsManager.textGray),
color: ColorsManager.textGray),
), ),
), ),
), ),
@ -99,9 +89,7 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
) )
: CustomSearchBar( : CustomSearchBar(
onSearchChanged: (query) { onSearchChanged: (query) {
context context.read<SpaceTreeBloc>().add(SearchQueryEvent(query));
.read<SpaceTreeBloc>()
.add(SearchQueryEvent(query));
}, },
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@ -117,18 +105,14 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
? Center( ? Center(
child: Text( child: Text(
'No results found', 'No results found',
style: Theme.of(context) style: Theme.of(context).textTheme.bodySmall!.copyWith(
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.lightGrayColor, color: ColorsManager.lightGrayColor,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
), ),
) )
: Scrollbar( : Scrollbar(
scrollbarOrientation: scrollbarOrientation: ScrollbarOrientation.left,
ScrollbarOrientation.left,
thumbVisibility: true, thumbVisibility: true,
controller: _scrollController, controller: _scrollController,
child: Padding( child: Padding(
@ -138,39 +122,30 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
shrinkWrap: true, shrinkWrap: true,
children: list children: list
.map( .map(
(community) => (community) => CustomExpansionTileSpaceTree(
CustomExpansionTileSpaceTree(
title: community.name, title: community.name,
isSelected: state isSelected: state.selectedCommunities
.selectedCommunities
.contains(community.uuid), .contains(community.uuid),
isSoldCheck: state isSoldCheck: state.selectedCommunities
.selectedCommunities
.contains(community.uuid), .contains(community.uuid),
onExpansionChanged: () { onExpansionChanged: () {
context context
.read<SpaceTreeBloc>() .read<SpaceTreeBloc>()
.add(OnCommunityExpanded( .add(OnCommunityExpanded(community.uuid));
community.uuid));
}, },
isExpanded: state isExpanded: state.expandedCommunities
.expandedCommunities
.contains(community.uuid), .contains(community.uuid),
onItemSelected: () { onItemSelected: () {
context context.read<SpaceTreeBloc>().add(
.read<SpaceTreeBloc>() OnCommunitySelected(
.add(OnCommunitySelected( community.uuid, community.spaces));
community.uuid,
community.spaces));
widget.onSelect(); widget.onSelect();
}, },
children: children: community.spaces.map((space) {
community.spaces.map((space) {
return CustomExpansionTileSpaceTree( return CustomExpansionTileSpaceTree(
title: space.name, title: space.name,
isExpanded: state isExpanded:
.expandedSpaces state.expandedSpaces.contains(space.uuid),
.contains(space.uuid),
onItemSelected: () { onItemSelected: () {
context.read<SpaceTreeBloc>().add( context.read<SpaceTreeBloc>().add(
OnSpaceSelected(community, space.uuid ?? '', OnSpaceSelected(community, space.uuid ?? '',
@ -178,20 +153,14 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
widget.onSelect(); widget.onSelect();
}, },
onExpansionChanged: () { onExpansionChanged: () {
context context.read<SpaceTreeBloc>().add(
.read<SpaceTreeBloc>() OnSpaceExpanded(
.add(OnSpaceExpanded( community.uuid, space.uuid ?? ''));
community.uuid,
space.uuid ?? ''));
}, },
isSelected: state isSelected:
.selectedSpaces state.selectedSpaces.contains(space.uuid) ||
.contains( state.soldCheck.contains(space.uuid),
space.uuid) || isSoldCheck: state.soldCheck.contains(space.uuid),
state.soldCheck
.contains(space.uuid),
isSoldCheck: state.soldCheck
.contains(space.uuid),
children: _buildNestedSpaces( children: _buildNestedSpaces(
context, state, space, community), context, state, space, community),
); );
@ -279,8 +248,8 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
BuildContext context, SpaceTreeState state, SpaceModel space, CommunityModel community) { BuildContext context, SpaceTreeState state, SpaceModel space, CommunityModel community) {
return space.children.map((child) { return space.children.map((child) {
return CustomExpansionTileSpaceTree( return CustomExpansionTileSpaceTree(
isSelected: state.selectedSpaces.contains(child.uuid) || isSelected:
state.soldCheck.contains(child.uuid), state.selectedSpaces.contains(child.uuid) || state.soldCheck.contains(child.uuid),
isSoldCheck: state.soldCheck.contains(child.uuid), isSoldCheck: state.soldCheck.contains(child.uuid),
title: child.name, title: child.name,
isExpanded: state.expandedSpaces.contains(child.uuid), isExpanded: state.expandedSpaces.contains(child.uuid),

View File

@ -24,6 +24,8 @@ class AddDeviceTypeWidget extends StatelessWidget {
final String spaceName; final String spaceName;
final bool isCreate; final bool isCreate;
final Function(List<Tag>, List<SubspaceModel>?)? onSave; final Function(List<Tag>, List<SubspaceModel>?)? onSave;
final List<Tag> projectTags;
const AddDeviceTypeWidget( const AddDeviceTypeWidget(
{super.key, {super.key,
@ -35,7 +37,8 @@ class AddDeviceTypeWidget extends StatelessWidget {
this.allTags, this.allTags,
this.spaceTags, this.spaceTags,
this.onSave, this.onSave,
required this.spaceName}); required this.spaceName,
required this.projectTags});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -134,7 +137,8 @@ class AddDeviceTypeWidget extends StatelessWidget {
spaceName: spaceName, spaceName: spaceName,
initialTags: initialTags, initialTags: initialTags,
title: dialogTitle, title: dialogTitle,
onSave: onSave), onSave: onSave,
projectTags: projectTags),
); );
} }
}, },

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
@ -19,8 +20,7 @@ import 'package:syncrow_web/services/space_mana_api.dart';
import 'package:syncrow_web/services/space_model_mang_api.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart';
import 'package:syncrow_web/utils/constants/action_enum.dart' as custom_action; import 'package:syncrow_web/utils/constants/action_enum.dart' as custom_action;
class SpaceManagementBloc class SpaceManagementBloc extends Bloc<SpaceManagementEvent, SpaceManagementState> {
extends Bloc<SpaceManagementEvent, SpaceManagementState> {
final CommunitySpaceManagementApi _api; final CommunitySpaceManagementApi _api;
final ProductApi _productApi; final ProductApi _productApi;
final SpaceModelManagementApi _spaceModelApi; final SpaceModelManagementApi _spaceModelApi;
@ -28,6 +28,7 @@ class SpaceManagementBloc
List<ProductModel>? _cachedProducts; List<ProductModel>? _cachedProducts;
List<SpaceTemplateModel>? _cachedSpaceModels; List<SpaceTemplateModel>? _cachedSpaceModels;
final SpaceTreeBloc _spaceTreeBloc; final SpaceTreeBloc _spaceTreeBloc;
List<Tag>? _cachedTags;
SpaceManagementBloc( SpaceManagementBloc(
this._api, this._api,
@ -52,42 +53,50 @@ class SpaceManagementBloc
Future<void> _updateSpaceModelCache( Future<void> _updateSpaceModelCache(
UpdateSpaceModelCache event, Emitter<SpaceManagementState> emit) async { UpdateSpaceModelCache event, Emitter<SpaceManagementState> emit) async {
if (_cachedSpaceModels != null) { final projectUuid = await ProjectManager.getProjectUUID() ?? '';
_cachedSpaceModels = _cachedSpaceModels!.map((model) {
return model.uuid == event.updatedModel.uuid List<SpaceTemplateModel> allSpaceModels = [];
? event.updatedModel
: model; bool hasNext = true;
}).toList(); int page = 1;
} else {
_cachedSpaceModels = await fetchSpaceModels(); while (hasNext) {
final spaceModels = await _spaceModelApi.listSpaceModels(page: page, projectId: projectUuid);
if (spaceModels.isNotEmpty) {
allSpaceModels.addAll(spaceModels);
page++;
} else {
hasNext = false;
}
} }
_cachedSpaceModels = allSpaceModels;
await fetchTags();
emit(SpaceModelLoaded( emit(SpaceModelLoaded(
communities: state is SpaceManagementLoaded communities:
? (state as SpaceManagementLoaded).communities state is SpaceManagementLoaded ? (state as SpaceManagementLoaded).communities : [],
: [], products: _cachedProducts ?? [],
products: _cachedProducts ?? [], spaceModels: List.from(_cachedSpaceModels ?? []),
spaceModels: List.from(_cachedSpaceModels ?? []), allTags: _cachedTags ?? []));
));
} }
void _deleteSpaceModelFromCache(DeleteSpaceModelFromCache event, void _deleteSpaceModelFromCache(
Emitter<SpaceManagementState> emit) async { DeleteSpaceModelFromCache event, Emitter<SpaceManagementState> emit) async {
if (_cachedSpaceModels != null) { if (_cachedSpaceModels != null) {
_cachedSpaceModels = _cachedSpaceModels! _cachedSpaceModels =
.where((model) => model.uuid != event.deletedUuid) _cachedSpaceModels!.where((model) => model.uuid != event.deletedUuid).toList();
.toList();
} else { } else {
_cachedSpaceModels = await fetchSpaceModels(); _cachedSpaceModels = await fetchSpaceModels();
} }
await fetchTags();
emit(SpaceModelLoaded( emit(SpaceModelLoaded(
communities: state is SpaceManagementLoaded communities:
? (state as SpaceManagementLoaded).communities state is SpaceManagementLoaded ? (state as SpaceManagementLoaded).communities : [],
: [], products: _cachedProducts ?? [],
products: _cachedProducts ?? [], spaceModels: List.from(_cachedSpaceModels ?? []),
spaceModels: List.from(_cachedSpaceModels ?? []), allTags: _cachedTags ?? []));
));
} }
void updateCachedSpaceModels(List<SpaceTemplateModel> updatedModels) { void updateCachedSpaceModels(List<SpaceTemplateModel> updatedModels) {
@ -112,8 +121,8 @@ class SpaceManagementBloc
int page = 1; int page = 1;
while (hasNext) { while (hasNext) {
final spaceModels = await _spaceModelApi.listSpaceModels( final spaceModels =
page: page, projectId: projectUuid); await _spaceModelApi.listSpaceModels(page: page, projectId: projectUuid);
if (spaceModels.isNotEmpty) { if (spaceModels.isNotEmpty) {
allSpaceModels.addAll(spaceModels); allSpaceModels.addAll(spaceModels);
page++; page++;
@ -130,6 +139,20 @@ class SpaceManagementBloc
} }
} }
Future<List<Tag>> fetchTags() async {
try {
if (_cachedTags != null) {
return _cachedTags!;
}
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
final allTags = await _spaceModelApi.listTags(projectId: projectUuid);
_cachedTags = allTags;
return allTags;
} catch (e) {
return [];
}
}
void _onUpdateCommunity( void _onUpdateCommunity(
UpdateCommunityEvent event, UpdateCommunityEvent event,
Emitter<SpaceManagementState> emit, Emitter<SpaceManagementState> emit,
@ -137,14 +160,13 @@ class SpaceManagementBloc
final previousState = state; final previousState = state;
try { try {
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
await fetchTags();
emit(SpaceManagementLoading()); emit(SpaceManagementLoading());
final success = await _api.updateCommunity( final success = await _api.updateCommunity(event.communityUuid, event.name, projectUuid);
event.communityUuid, event.name, projectUuid);
if (success) { if (success) {
if (previousState is SpaceManagementLoaded) { if (previousState is SpaceManagementLoaded) {
final updatedCommunities = final updatedCommunities = List<CommunityModel>.from(previousState.communities);
List<CommunityModel>.from(previousState.communities);
for (var community in updatedCommunities) { for (var community in updatedCommunities) {
if (community.uuid == event.communityUuid) { if (community.uuid == event.communityUuid) {
community.name = event.name; community.name = event.name;
@ -157,11 +179,11 @@ class SpaceManagementBloc
var prevSpaceModels = await fetchSpaceModels(); var prevSpaceModels = await fetchSpaceModels();
emit(SpaceManagementLoaded( emit(SpaceManagementLoaded(
communities: updatedCommunities, communities: updatedCommunities,
products: previousState.products, products: previousState.products,
selectedCommunity: previousState.selectedCommunity, selectedCommunity: previousState.selectedCommunity,
spaceModels: prevSpaceModels, spaceModels: prevSpaceModels,
)); allTags: _cachedTags ?? []));
} }
} else { } else {
emit(const SpaceManagementError('Failed to update the community.')); emit(const SpaceManagementError('Failed to update the community.'));
@ -189,8 +211,7 @@ class SpaceManagementBloc
} }
} }
Future<List<SpaceModel>> _fetchSpacesForCommunity( Future<List<SpaceModel>> _fetchSpacesForCommunity(String communityUuid) async {
String communityUuid) async {
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
return await _api.getSpaceHierarchy(communityUuid, projectUuid); return await _api.getSpaceHierarchy(communityUuid, projectUuid);
@ -201,7 +222,7 @@ class SpaceManagementBloc
Emitter<SpaceManagementState> emit, Emitter<SpaceManagementState> emit,
) async { ) async {
try { try {
final previousState = state; await fetchTags();
if (event.communities.isEmpty) { if (event.communities.isEmpty) {
emit(const SpaceManagementError('No communities provided.')); emit(const SpaceManagementError('No communities provided.'));
@ -211,33 +232,33 @@ class SpaceManagementBloc
var prevSpaceModels = await fetchSpaceModels(); var prevSpaceModels = await fetchSpaceModels();
emit(BlankState( emit(BlankState(
communities: event.communities, communities: event.communities,
products: _cachedProducts ?? [], products: _cachedProducts ?? [],
spaceModels: prevSpaceModels, spaceModels: prevSpaceModels,
)); allTags: _cachedTags ?? []));
} catch (error) { } catch (error) {
emit(SpaceManagementError('Error loading communities: $error')); emit(SpaceManagementError('Error loading communities: $error'));
} }
} }
Future<void> _onBlankState( Future<void> _onBlankState(BlankStateEvent event, Emitter<SpaceManagementState> emit) async {
BlankStateEvent event, Emitter<SpaceManagementState> emit) async {
try { try {
final previousState = state; final previousState = state;
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
var spaceBloc = event.context.read<SpaceTreeBloc>(); var spaceBloc = event.context.read<SpaceTreeBloc>();
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc); List<CommunityModel> communities = await _waitForCommunityList(spaceBloc);
await fetchSpaceModels();
await fetchTags();
var prevSpaceModels = await fetchSpaceModels(); var prevSpaceModels = await fetchSpaceModels();
if (previousState is SpaceManagementLoaded || if (previousState is SpaceManagementLoaded || previousState is BlankState) {
previousState is BlankState) {
final prevCommunities = (previousState as dynamic).communities ?? []; final prevCommunities = (previousState as dynamic).communities ?? [];
emit(BlankState( emit(BlankState(
communities: List<CommunityModel>.from(prevCommunities), communities: List<CommunityModel>.from(prevCommunities),
products: _cachedProducts ?? [], products: _cachedProducts ?? [],
spaceModels: prevSpaceModels, spaceModels: prevSpaceModels,
)); allTags: _cachedTags ?? []));
return; return;
} }
@ -246,8 +267,7 @@ class SpaceManagementBloc
List<CommunityModel> updatedCommunities = await Future.wait( List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async { communities.map((community) async {
List<SpaceModel> spaces = List<SpaceModel> spaces = await _fetchSpacesForCommunity(community.uuid);
await _fetchSpacesForCommunity(community.uuid);
return CommunityModel( return CommunityModel(
uuid: community.uuid, uuid: community.uuid,
createdAt: community.createdAt, createdAt: community.createdAt,
@ -267,6 +287,7 @@ class SpaceManagementBloc
spaceModels: prevSpaceModels, spaceModels: prevSpaceModels,
communities: communities, communities: communities,
products: _cachedProducts ?? [], products: _cachedProducts ?? [],
allTags: _cachedTags ?? [],
)); ));
} catch (error) { } catch (error) {
emit(SpaceManagementError('Error loading communities: $error')); emit(SpaceManagementError('Error loading communities: $error'));
@ -279,21 +300,20 @@ class SpaceManagementBloc
) async { ) async {
var spaceBloc = event.context.read<SpaceTreeBloc>(); var spaceBloc = event.context.read<SpaceTreeBloc>();
_onloadProducts(); _onloadProducts();
await fetchTags();
// Wait until `communityList` is loaded // Wait until `communityList` is loaded
List<CommunityModel> communities = await _waitForCommunityList(spaceBloc); List<CommunityModel> communities = await _waitForCommunityList(spaceBloc);
// Fetch space models after communities are available // Fetch space models after communities are available
final prevSpaceModels = await fetchSpaceModels(); final prevSpaceModels = await fetchSpaceModels();
emit(SpaceManagementLoaded( emit(SpaceManagementLoaded(
communities: communities, communities: communities,
products: _cachedProducts ?? [], products: _cachedProducts ?? [],
spaceModels: prevSpaceModels, spaceModels: prevSpaceModels,
)); allTags: _cachedTags ?? []));
} }
Future<List<CommunityModel>> _waitForCommunityList( Future<List<CommunityModel>> _waitForCommunityList(SpaceTreeBloc spaceBloc) async {
SpaceTreeBloc spaceBloc) async {
// Check if communityList is already populated // Check if communityList is already populated
if (spaceBloc.state.communityList.isNotEmpty) { if (spaceBloc.state.communityList.isNotEmpty) {
return spaceBloc.state.communityList; return spaceBloc.state.communityList;
@ -320,8 +340,7 @@ class SpaceManagementBloc
emit(SpaceManagementLoading()); emit(SpaceManagementLoading());
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
final success = final success = await _api.deleteCommunity(event.communityUuid, projectUuid);
await _api.deleteCommunity(event.communityUuid, projectUuid);
if (success) { if (success) {
// add(LoadCommunityAndSpacesEvent()); // add(LoadCommunityAndSpacesEvent());
} else { } else {
@ -342,14 +361,13 @@ class SpaceManagementBloc
try { try {
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
await fetchTags();
CommunityModel? newCommunity = await _api.createCommunity( CommunityModel? newCommunity =
event.name, event.description, projectUuid); await _api.createCommunity(event.name, event.description, projectUuid);
var prevSpaceModels = await fetchSpaceModels(); var prevSpaceModels = await fetchSpaceModels();
if (newCommunity != null) { if (newCommunity != null) {
if (previousState is SpaceManagementLoaded || if (previousState is SpaceManagementLoaded || previousState is BlankState) {
previousState is BlankState) {
final prevCommunities = List<CommunityModel>.from( final prevCommunities = List<CommunityModel>.from(
(previousState as dynamic).communities, (previousState as dynamic).communities,
); );
@ -357,11 +375,13 @@ class SpaceManagementBloc
_spaceTreeBloc.add(OnCommunityAdded(newCommunity)); _spaceTreeBloc.add(OnCommunityAdded(newCommunity));
emit(SpaceManagementLoaded( emit(SpaceManagementLoaded(
spaceModels: prevSpaceModels, spaceModels: prevSpaceModels,
communities: updatedCommunities, communities: updatedCommunities,
products: _cachedProducts ?? [], products: _cachedProducts ?? [],
selectedCommunity: newCommunity, selectedCommunity: newCommunity,
selectedSpace: null)); selectedSpace: null,
allTags: _cachedTags ?? [],
));
} }
} else { } else {
emit(const SpaceManagementError('Error creating community')); emit(const SpaceManagementError('Error creating community'));
@ -401,11 +421,12 @@ class SpaceManagementBloc
required Emitter<SpaceManagementState> emit, required Emitter<SpaceManagementState> emit,
CommunityModel? selectedCommunity, CommunityModel? selectedCommunity,
SpaceModel? selectedSpace, SpaceModel? selectedSpace,
}) { }) async {
final previousState = state; final previousState = state;
emit(SpaceManagementLoading()); emit(SpaceManagementLoading());
try { try {
await fetchTags();
if (previousState is SpaceManagementLoaded || if (previousState is SpaceManagementLoaded ||
previousState is BlankState || previousState is BlankState ||
previousState is SpaceModelLoaded) { previousState is SpaceModelLoaded) {
@ -421,7 +442,8 @@ class SpaceManagementBloc
products: _cachedProducts ?? [], products: _cachedProducts ?? [],
selectedCommunity: selectedCommunity, selectedCommunity: selectedCommunity,
selectedSpace: selectedSpace, selectedSpace: selectedSpace,
spaceModels: spaceModels)); spaceModels: spaceModels,
allTags: _cachedTags ?? []));
} }
} catch (e) { } catch (e) {
emit(SpaceManagementError('Error updating state: $e')); emit(SpaceManagementError('Error updating state: $e'));
@ -437,7 +459,7 @@ class SpaceManagementBloc
try { try {
final updatedSpaces = final updatedSpaces =
await saveSpacesHierarchically(event.spaces, event.communityUuid); await saveSpacesHierarchically(event.context, event.spaces, event.communityUuid);
final allSpaces = await _fetchSpacesForCommunity(event.communityUuid); final allSpaces = await _fetchSpacesForCommunity(event.communityUuid);
@ -467,29 +489,35 @@ class SpaceManagementBloc
Emitter<SpaceManagementState> emit, Emitter<SpaceManagementState> emit,
) async { ) async {
var prevSpaceModels = await fetchSpaceModels(); var prevSpaceModels = await fetchSpaceModels();
await fetchTags();
final communities = List<CommunityModel>.from(previousState.communities); final communities = List<CommunityModel>.from(previousState.communities);
for (var community in communities) { for (var community in communities) {
if (community.uuid == communityUuid) { if (community.uuid == communityUuid) {
community.spaces = allSpaces; community.spaces = allSpaces;
_spaceTreeBloc.add(OnCommunityUpdated(community)); _spaceTreeBloc.add(InitialEvent());
emit(SpaceManagementLoaded( emit(SpaceManagementLoaded(
communities: communities, communities: communities,
products: _cachedProducts ?? [], products: _cachedProducts ?? [],
selectedCommunity: community, selectedCommunity: community,
selectedSpace: null, selectedSpace: null,
spaceModels: prevSpaceModels)); spaceModels: prevSpaceModels,
allTags: _cachedTags ?? []));
return; return;
} }
} }
} }
Future<List<SpaceModel>> saveSpacesHierarchically( Future<List<SpaceModel>> saveSpacesHierarchically(
List<SpaceModel> spaces, String communityUuid) async { BuildContext context, List<SpaceModel> spaces, String communityUuid) async {
final orderedSpaces = flattenHierarchy(spaces); final orderedSpaces = flattenHierarchy(spaces);
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
var spaceBloc = context.read<SpaceTreeBloc>();
List<CommunityModel> communities = spaceBloc.state.communityList;
CommunityModel? selectedCommunity = communities.firstWhere(
(community) => community.uuid == communityUuid,
);
final parentsToDelete = orderedSpaces.where((space) => final parentsToDelete = orderedSpaces.where((space) =>
space.status == SpaceStatus.deleted && space.status == SpaceStatus.deleted &&
@ -511,8 +539,13 @@ class SpaceManagementBloc
if (space.uuid != null && space.uuid!.isNotEmpty) { if (space.uuid != null && space.uuid!.isNotEmpty) {
List<TagModelUpdate> tagUpdates = []; List<TagModelUpdate> tagUpdates = [];
final prevSpace = List<SpaceModel> matchedSpaces =
await _api.getSpace(communityUuid, space.uuid!, projectUuid); selectedCommunity.spaces.where((space) => space.uuid == space.uuid).toList();
if (matchedSpaces.isEmpty) continue;
final prevSpace = matchedSpaces[0];
final List<UpdateSubspaceTemplateModel> subspaceUpdates = []; final List<UpdateSubspaceTemplateModel> subspaceUpdates = [];
final List<SubspaceModel>? prevSubspaces = prevSpace?.subspaces; final List<SubspaceModel>? prevSubspaces = prevSpace?.subspaces;
final List<SubspaceModel>? newSubspaces = space.subspaces; final List<SubspaceModel>? newSubspaces = space.subspaces;
@ -522,19 +555,17 @@ class SpaceManagementBloc
if (prevSubspaces != null || newSubspaces != null) { if (prevSubspaces != null || newSubspaces != null) {
if (prevSubspaces != null && newSubspaces != null) { if (prevSubspaces != null && newSubspaces != null) {
for (var prevSubspace in prevSubspaces) { for (var prevSubspace in prevSubspaces) {
final existsInNew = newSubspaces final existsInNew =
.any((subspace) => subspace.uuid == prevSubspace.uuid); newSubspaces.any((subspace) => subspace.uuid == prevSubspace.uuid);
if (!existsInNew) { if (!existsInNew) {
subspaceUpdates.add(UpdateSubspaceTemplateModel( subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: custom_action.Action.delete, action: custom_action.Action.delete, uuid: prevSubspace.uuid));
uuid: prevSubspace.uuid));
} }
} }
} else if (prevSubspaces != null && newSubspaces == null) { } else if (prevSubspaces != null && newSubspaces == null) {
for (var prevSubspace in prevSubspaces) { for (var prevSubspace in prevSubspaces) {
subspaceUpdates.add(UpdateSubspaceTemplateModel( subspaceUpdates.add(UpdateSubspaceTemplateModel(
action: custom_action.Action.delete, action: custom_action.Action.delete, uuid: prevSubspace.uuid));
uuid: prevSubspace.uuid));
} }
} }
@ -548,7 +579,7 @@ class SpaceManagementBloc
for (var tag in newSubspace.tags!) { for (var tag in newSubspace.tags!) {
tagUpdates.add(TagModelUpdate( tagUpdates.add(TagModelUpdate(
action: custom_action.Action.add, action: custom_action.Action.add,
uuid: tag.uuid == '' ? null : tag.uuid, newTagUuid: tag.uuid == '' ? null : tag.uuid,
tag: tag.tag, tag: tag.tag,
productUuid: tag.product?.uuid)); productUuid: tag.product?.uuid));
} }
@ -562,9 +593,7 @@ class SpaceManagementBloc
} }
if (prevSubspaces != null && newSubspaces != null) { if (prevSubspaces != null && newSubspaces != null) {
final newSubspaceMap = { final newSubspaceMap = {for (var subspace in newSubspaces) subspace.uuid: subspace};
for (var subspace in newSubspaces) subspace.uuid: subspace
};
for (var prevSubspace in prevSubspaces) { for (var prevSubspace in prevSubspaces) {
final newSubspace = newSubspaceMap[prevSubspace.uuid]; final newSubspace = newSubspaceMap[prevSubspace.uuid];
@ -593,6 +622,7 @@ class SpaceManagementBloc
subspaces: subspaceUpdates, subspaces: subspaceUpdates,
tags: tagUpdates, tags: tagUpdates,
direction: space.incomingConnection?.direction, direction: space.incomingConnection?.direction,
spaceModelUuid: space.spaceModel?.uuid,
projectId: projectUuid); projectId: projectUuid);
} else { } else {
// Call create if the space does not have a UUID // Call create if the space does not have a UUID
@ -601,10 +631,8 @@ class SpaceManagementBloc
: []; : [];
final createSubspaceBodyModels = space.subspaces?.map((subspace) { final createSubspaceBodyModels = space.subspaces?.map((subspace) {
final tagBodyModels = subspace.tags final tagBodyModels =
?.map((tag) => tag.toCreateTagBodyModel()) subspace.tags?.map((tag) => tag.toCreateTagBodyModel()).toList() ?? [];
.toList() ??
[];
return CreateSubspaceModel() return CreateSubspaceModel()
..subspaceName = subspace.subspaceName ..subspaceName = subspace.subspaceName
..tags = tagBodyModels; ..tags = tagBodyModels;
@ -657,12 +685,11 @@ class SpaceManagementBloc
return result.toList(); // Convert back to a list return result.toList(); // Convert back to a list
} }
void _onLoadSpaceModel( void _onLoadSpaceModel(SpaceModelLoadEvent event, Emitter<SpaceManagementState> emit) async {
SpaceModelLoadEvent event, Emitter<SpaceManagementState> emit) async {
emit(SpaceManagementLoading()); emit(SpaceManagementLoading());
try { try {
var prevState = state; await fetchTags();
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
var spaceBloc = event.context.read<SpaceTreeBloc>(); var spaceBloc = event.context.read<SpaceTreeBloc>();
List<CommunityModel> communities = spaceBloc.state.communityList; List<CommunityModel> communities = spaceBloc.state.communityList;
@ -674,8 +701,7 @@ class SpaceManagementBloc
List<CommunityModel> updatedCommunities = await Future.wait( List<CommunityModel> updatedCommunities = await Future.wait(
communities.map((community) async { communities.map((community) async {
List<SpaceModel> spaces = List<SpaceModel> spaces = await _fetchSpacesForCommunity(community.uuid);
await _fetchSpacesForCommunity(community.uuid);
return CommunityModel( return CommunityModel(
uuid: community.uuid, uuid: community.uuid,
createdAt: community.createdAt, createdAt: community.createdAt,
@ -692,10 +718,10 @@ class SpaceManagementBloc
} }
emit(SpaceModelLoaded( emit(SpaceModelLoaded(
communities: communities, communities: communities,
products: _cachedProducts ?? [], products: _cachedProducts ?? [],
spaceModels: prevSpaceModels, spaceModels: prevSpaceModels,
)); allTags: _cachedTags ?? []));
} catch (e) { } catch (e) {
emit(SpaceManagementError('Error loading communities and spaces: $e')); emit(SpaceManagementError('Error loading communities and spaces: $e'));
} }
@ -713,7 +739,7 @@ class SpaceManagementBloc
tagUpdates.add(TagModelUpdate( tagUpdates.add(TagModelUpdate(
action: custom_action.Action.add, action: custom_action.Action.add,
tag: newTag.tag, tag: newTag.tag,
uuid: newTag.uuid, newTagUuid: newTag.uuid,
productUuid: newTag.product?.uuid, productUuid: newTag.product?.uuid,
)); ));
} }
@ -724,17 +750,14 @@ class SpaceManagementBloc
// Case 1: Tags deleted // Case 1: Tags deleted
if (prevTags != null && newTags != null) { if (prevTags != null && newTags != null) {
for (var prevTag in prevTags) { for (var prevTag in prevTags) {
final existsInNew = final existsInNew = newTags.any((newTag) => newTag.uuid == prevTag.uuid);
newTags.any((newTag) => newTag.uuid == prevTag.uuid);
if (!existsInNew) { if (!existsInNew) {
tagUpdates.add(TagModelUpdate( tagUpdates.add(TagModelUpdate(action: custom_action.Action.delete, uuid: prevTag.uuid));
action: custom_action.Action.delete, uuid: prevTag.uuid));
} }
} }
} else if (prevTags != null && newTags == null) { } else if (prevTags != null && newTags == null) {
for (var prevTag in prevTags) { for (var prevTag in prevTags) {
tagUpdates.add(TagModelUpdate( tagUpdates.add(TagModelUpdate(action: custom_action.Action.delete, uuid: prevTag.uuid));
action: custom_action.Action.delete, uuid: prevTag.uuid));
} }
} }
@ -749,7 +772,7 @@ class SpaceManagementBloc
tagUpdates.add(TagModelUpdate( tagUpdates.add(TagModelUpdate(
action: custom_action.Action.add, action: custom_action.Action.add,
tag: newTag.tag, tag: newTag.tag,
uuid: newTag.uuid == '' ? null : newTag.uuid, newTagUuid: newTag.uuid == '' ? null : newTag.uuid,
productUuid: newTag.product?.uuid)); productUuid: newTag.product?.uuid));
processedTags.add(newTag.tag); processedTags.add(newTag.tag);
} }
@ -766,6 +789,7 @@ class SpaceManagementBloc
tagUpdates.add(TagModelUpdate( tagUpdates.add(TagModelUpdate(
action: custom_action.Action.update, action: custom_action.Action.update,
uuid: newTag.uuid, uuid: newTag.uuid,
newTagUuid: newTag.uuid,
tag: newTag.tag, tag: newTag.tag,
)); ));
} else {} } else {}

View File

@ -58,14 +58,16 @@ class CreateSpaceEvent extends SpaceManagementEvent {
class SaveSpacesEvent extends SpaceManagementEvent { class SaveSpacesEvent extends SpaceManagementEvent {
final List<SpaceModel> spaces; final List<SpaceModel> spaces;
final String communityUuid; final String communityUuid;
final BuildContext context;
const SaveSpacesEvent({ const SaveSpacesEvent(
this.context, {
required this.spaces, required this.spaces,
required this.communityUuid, required this.communityUuid,
}); });
@override @override
List<Object> get props => [spaces, communityUuid]; List<Object> get props => [spaces, communityUuid, context];
} }
class UpdateSpacePositionEvent extends SpaceManagementEvent { class UpdateSpacePositionEvent extends SpaceManagementEvent {
@ -170,4 +172,4 @@ class UpdateSpaceModelCache extends SpaceManagementEvent {
class DeleteSpaceModelFromCache extends SpaceManagementEvent { class DeleteSpaceModelFromCache extends SpaceManagementEvent {
final String deletedUuid; final String deletedUuid;
DeleteSpaceModelFromCache(this.deletedUuid); DeleteSpaceModelFromCache(this.deletedUuid);
} }

View File

@ -2,6 +2,7 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/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/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';
abstract class SpaceManagementState extends Equatable { abstract class SpaceManagementState extends Equatable {
@ -21,22 +22,25 @@ class SpaceManagementLoaded extends SpaceManagementState {
CommunityModel? selectedCommunity; CommunityModel? selectedCommunity;
SpaceModel? selectedSpace; SpaceModel? selectedSpace;
List<SpaceTemplateModel>? spaceModels; List<SpaceTemplateModel>? spaceModels;
List<Tag> allTags;
SpaceManagementLoaded( SpaceManagementLoaded(
{required this.communities, {required this.communities,
required this.products, required this.products,
this.selectedCommunity, this.selectedCommunity,
this.selectedSpace, this.selectedSpace,
this.spaceModels}); this.spaceModels,
required this.allTags});
} }
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; List<SpaceTemplateModel>? spaceModels;
final List<Tag> allTags;
BlankState( BlankState(
{required this.communities, required this.products, this.spaceModels}); {required this.communities, required this.products, this.spaceModels, required this.allTags});
} }
class SpaceCreationSuccess extends SpaceManagementState { class SpaceCreationSuccess extends SpaceManagementState {
@ -61,14 +65,14 @@ class SpaceModelLoaded extends SpaceManagementState {
List<SpaceTemplateModel> spaceModels; List<SpaceTemplateModel> spaceModels;
final List<ProductModel> products; final List<ProductModel> products;
final List<CommunityModel> communities; final List<CommunityModel> communities;
final List<Tag> allTags;
SpaceModelLoaded({ SpaceModelLoaded(
required this.communities, {required this.communities,
required this.products, required this.products,
required this.spaceModels, required this.spaceModels,
}); required this.allTags});
@override @override
List<Object> get props => [communities, products, spaceModels]; List<Object> get props => [communities, products, spaceModels, allTags];
} }

View File

@ -14,6 +14,7 @@ class SpaceModel {
String? icon; String? icon;
final String? spaceTuyaUuid; final String? spaceTuyaUuid;
String name; String name;
String? lastThreeParents;
final bool isPrivate; final bool isPrivate;
final String? invitationCode; final String? invitationCode;
SpaceModel? parent; SpaceModel? parent;
@ -33,6 +34,7 @@ class SpaceModel {
SpaceModel({ SpaceModel({
this.uuid, this.uuid,
String? internalId, String? internalId,
this.lastThreeParents,
this.spaceTuyaUuid, this.spaceTuyaUuid,
required this.icon, required this.icon,
required this.name, required this.name,
@ -67,6 +69,7 @@ class SpaceModel {
internalId: internalId, internalId: internalId,
uuid: json['uuid'] ?? '', uuid: json['uuid'] ?? '',
name: json['spaceName'], name: json['spaceName'],
lastThreeParents: json['lastThreeParents'],
isPrivate: json['isPrivate'] ?? false, isPrivate: json['isPrivate'] ?? false,
invitationCode: json['invitationCode'], invitationCode: json['invitationCode'],
subspaces: (json['subspaces'] as List<dynamic>?) subspaces: (json['subspaces'] as List<dynamic>?)
@ -125,6 +128,7 @@ class SpaceModel {
'uuid': uuid ?? '', 'uuid': uuid ?? '',
'spaceTuyaUuid': spaceTuyaUuid, 'spaceTuyaUuid': spaceTuyaUuid,
'name': name, 'name': name,
'lastThreeParents': lastThreeParents,
'isPrivate': isPrivate, 'isPrivate': isPrivate,
'invitationCode': invitationCode, 'invitationCode': invitationCode,
'parent': parent?.uuid, 'parent': parent?.uuid,

View File

@ -25,22 +25,21 @@ class Tag extends BaseTag {
return Tag( return Tag(
uuid: json['uuid'] ?? '', uuid: json['uuid'] ?? '',
internalId: internalId, internalId: internalId,
tag: json['tag'] ?? '', tag: json['name'] ?? '',
product: json['product'] != null product: json['product'] != null ? ProductModel.fromMap(json['product']) : null,
? ProductModel.fromMap(json['product'])
: null,
); );
} }
@override @override
Tag copyWith({ Tag copyWith({
String? uuid,
String? tag, String? tag,
ProductModel? product, ProductModel? product,
String? location, String? location,
String? internalId, String? internalId,
}) { }) {
return Tag( return Tag(
uuid: uuid, uuid: uuid ?? this.uuid,
tag: tag ?? this.tag, tag: tag ?? this.tag,
product: product ?? this.product, product: product ?? this.product,
location: location ?? this.location, location: location ?? this.location,
@ -60,7 +59,7 @@ class Tag extends BaseTag {
extension TagModelExtensions on Tag { extension TagModelExtensions on Tag {
TagBodyModel toTagBodyModel() { TagBodyModel toTagBodyModel() {
return TagBodyModel() return TagBodyModel()
..uuid = uuid ?? '' ..uuid = uuid
..tag = tag ?? '' ..tag = tag ?? ''
..productUuid = product?.uuid; ..productUuid = product?.uuid;
} }

View File

@ -1,5 +1,3 @@
import 'dart:developer';
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/device_managment/shared/navigate_home_grid_view.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
@ -62,9 +60,9 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
selectedSpace: null, selectedSpace: null,
products: state.products, products: state.products,
shouldNavigateToSpaceModelPage: false, shouldNavigateToSpaceModelPage: false,
projectTags: state.allTags,
); );
} else if (state is SpaceManagementLoaded) { } else if (state is SpaceManagementLoaded) {
return LoadedSpaceView( return LoadedSpaceView(
communities: state.communities, communities: state.communities,
selectedCommunity: state.selectedCommunity, selectedCommunity: state.selectedCommunity,
@ -72,6 +70,7 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
products: state.products, products: state.products,
spaceModels: state.spaceModels, spaceModels: state.spaceModels,
shouldNavigateToSpaceModelPage: false, shouldNavigateToSpaceModelPage: false,
projectTags: state.allTags,
); );
} else if (state is SpaceModelLoaded) { } else if (state is SpaceModelLoaded) {
return LoadedSpaceView( return LoadedSpaceView(
@ -79,6 +78,7 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
products: state.products, products: state.products,
spaceModels: state.spaceModels, spaceModels: state.spaceModels,
shouldNavigateToSpaceModelPage: true, shouldNavigateToSpaceModelPage: true,
projectTags: state.allTags,
); );
} else if (state is SpaceManagementError) { } else if (state is SpaceManagementError) {
return Center(child: Text('Error: ${state.errorMessage}')); return Center(child: Text('Error: ${state.errorMessage}'));

View File

@ -36,16 +36,17 @@ class CommunityStructureArea extends StatefulWidget {
final List<CommunityModel> communities; final List<CommunityModel> communities;
final List<SpaceModel> spaces; final List<SpaceModel> spaces;
final List<SpaceTemplateModel>? spaceModels; final List<SpaceTemplateModel>? spaceModels;
final List<Tag> projectTags;
CommunityStructureArea({ CommunityStructureArea(
this.selectedCommunity, {this.selectedCommunity,
this.selectedSpace, this.selectedSpace,
required this.communities, required this.communities,
this.products, this.products,
required this.spaces, required this.spaces,
this.onSpaceSelected, this.onSpaceSelected,
this.spaceModels, this.spaceModels,
}); required this.projectTags});
@override @override
_CommunityStructureAreaState createState() => _CommunityStructureAreaState(); _CommunityStructureAreaState createState() => _CommunityStructureAreaState();
@ -64,8 +65,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void initState() { void initState() {
super.initState(); super.initState();
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections = connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces(); _adjustCanvasSizeForSpaces();
_nameController = TextEditingController( _nameController = TextEditingController(
text: widget.selectedCommunity?.name ?? '', text: widget.selectedCommunity?.name ?? '',
@ -92,14 +92,12 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
if (oldWidget.spaces != widget.spaces) { if (oldWidget.spaces != widget.spaces) {
setState(() { setState(() {
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections = connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces(); _adjustCanvasSizeForSpaces();
}); });
} }
if (widget.selectedSpace != oldWidget.selectedSpace && if (widget.selectedSpace != oldWidget.selectedSpace && widget.selectedSpace != null) {
widget.selectedSpace != null) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_moveToSpace(widget.selectedSpace!); _moveToSpace(widget.selectedSpace!);
}); });
@ -182,8 +180,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
connection, widget.selectedSpace) connection, widget.selectedSpace)
? 1.0 ? 1.0
: 0.3, // Adjust opacity : 0.3, // Adjust opacity
child: CustomPaint( child: CustomPaint(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 &&
@ -193,15 +190,12 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
top: entry.value.position.dy, top: entry.value.position.dy,
child: SpaceCardWidget( child: SpaceCardWidget(
index: entry.key, index: entry.key,
onButtonTap: (int index, Offset newPosition, onButtonTap: (int index, Offset newPosition, String direction) {
String direction) { _showCreateSpaceDialog(screenSize,
_showCreateSpaceDialog( position: spaces[index].position + newPosition,
screenSize, parentIndex: index,
position: direction: direction,
spaces[index].position + newPosition, projectTags: widget.projectTags);
parentIndex: index,
direction: direction,
);
}, },
position: entry.value.position, position: entry.value.position,
isHovered: entry.value.isHovered, isHovered: entry.value.isHovered,
@ -211,9 +205,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
_updateNodePosition(entry.value, newPosition); _updateNodePosition(entry.value, newPosition);
}, },
buildSpaceContainer: (int index) { buildSpaceContainer: (int index) {
final bool isHighlighted = final bool isHighlighted = SpaceHelper.isHighlightedSpace(
SpaceHelper.isHighlightedSpace( spaces[index], widget.selectedSpace);
spaces[index], widget.selectedSpace);
return Opacity( return Opacity(
opacity: isHighlighted ? 1.0 : 0.3, opacity: isHighlighted ? 1.0 : 0.3,
@ -238,7 +231,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
onTap: () { onTap: () {
_showCreateSpaceDialog(screenSize, _showCreateSpaceDialog(screenSize,
canvasHeight: canvasHeight, canvasHeight: canvasHeight,
canvasWidth: canvasWidth); canvasWidth: canvasWidth,
projectTags: widget.projectTags);
}, },
), ),
), ),
@ -292,26 +286,22 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
int? parentIndex, int? parentIndex,
String? direction, String? direction,
double? canvasWidth, double? canvasWidth,
double? canvasHeight}) { double? canvasHeight,
required List<Tag> projectTags}) {
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return CreateSpaceDialog( return CreateSpaceDialog(
products: widget.products, products: widget.products,
spaceModels: widget.spaceModels, spaceModels: widget.spaceModels,
allTags: allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
parentSpace: parentIndex != null ? spaces[parentIndex] : null, parentSpace: parentIndex != null ? spaces[parentIndex] : null,
onCreateSpace: (String name, projectTags: projectTags,
String icon, onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts,
List<SelectedProduct> selectedProducts, SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) {
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 ?? ConnectionHelper.getCenterPosition(screenSize);
position ?? ConnectionHelper.getCenterPosition(screenSize);
SpaceModel newSpace = SpaceModel( SpaceModel newSpace = SpaceModel(
name: name, name: name,
icon: icon, icon: icon,
@ -356,21 +346,17 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
spaceModels: widget.spaceModels, spaceModels: widget.spaceModels,
name: widget.selectedSpace!.name, name: widget.selectedSpace!.name,
icon: widget.selectedSpace!.icon, icon: widget.selectedSpace!.icon,
parentSpace: SpaceHelper.findSpaceByInternalId( projectTags: widget.projectTags,
widget.selectedSpace?.parent?.internalId, spaces), parentSpace:
SpaceHelper.findSpaceByInternalId(widget.selectedSpace?.parent?.internalId, spaces),
editSpace: widget.selectedSpace, editSpace: widget.selectedSpace,
currentSpaceModel: widget.selectedSpace?.spaceModel, currentSpaceModel: widget.selectedSpace?.spaceModel,
tags: widget.selectedSpace?.tags, tags: widget.selectedSpace?.tags,
subspaces: widget.selectedSpace?.subspaces, subspaces: widget.selectedSpace?.subspaces,
isEdit: true, isEdit: true,
allTags: TagHelper.getAllTagValues( allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
widget.communities, widget.spaceModels), onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts,
onCreateSpace: (String name, SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) {
String icon,
List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel,
List<SubspaceModel>? subspaces,
List<Tag>? tags) {
setState(() { setState(() {
// Update the space's properties // Update the space's properties
widget.selectedSpace!.name = name; widget.selectedSpace!.name = name;
@ -380,8 +366,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
widget.selectedSpace!.tags = tags; widget.selectedSpace!.tags = tags;
if (widget.selectedSpace!.status != SpaceStatus.newSpace) { if (widget.selectedSpace!.status != SpaceStatus.newSpace) {
widget.selectedSpace!.status = widget.selectedSpace!.status = SpaceStatus.modified; // Mark as modified
SpaceStatus.modified; // Mark as modified
} }
for (var space in spaces) { for (var space in spaces) {
@ -411,8 +396,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
List<SpaceModel> result = []; List<SpaceModel> result = [];
void flatten(SpaceModel space) { void flatten(SpaceModel space) {
if (space.status == SpaceStatus.deleted || if (space.status == SpaceStatus.deleted || space.status == SpaceStatus.parentDeleted) {
space.status == SpaceStatus.parentDeleted) {
return; return;
} }
result.add(space); result.add(space);
@ -476,6 +460,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
String communityUuid = widget.selectedCommunity!.uuid; String communityUuid = widget.selectedCommunity!.uuid;
context.read<SpaceManagementBloc>().add(SaveSpacesEvent( context.read<SpaceManagementBloc>().add(SaveSpacesEvent(
context,
spaces: spacesToSave, spaces: spacesToSave,
communityUuid: communityUuid, communityUuid: communityUuid,
)); ));
@ -527,16 +512,13 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void _selectSpace(BuildContext context, SpaceModel space) { void _selectSpace(BuildContext context, SpaceModel space) {
context.read<SpaceManagementBloc>().add( context.read<SpaceManagementBloc>().add(
SelectSpaceEvent( SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: space),
selectedCommunity: widget.selectedCommunity,
selectedSpace: space),
); );
} }
void _deselectSpace(BuildContext context) { void _deselectSpace(BuildContext context) {
context.read<SpaceManagementBloc>().add( context.read<SpaceManagementBloc>().add(
SelectSpaceEvent( SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: null),
selectedCommunity: widget.selectedCommunity, selectedSpace: null),
); );
} }
@ -625,19 +607,16 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
const double horizontalGap = 200.0; const double horizontalGap = 200.0;
const double verticalGap = 100.0; const double verticalGap = 100.0;
SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition, SpaceModel duplicateRecursive(
SpaceModel? duplicatedParent) { SpaceModel original, Offset parentPosition, SpaceModel? duplicatedParent) {
Offset newPosition = Offset newPosition = Offset(parentPosition.dx + horizontalGap, original.position.dy);
Offset(parentPosition.dx + horizontalGap, original.position.dy);
while (spaces.any((s) => while (spaces.any((s) =>
(s.position - newPosition).distance < horizontalGap && (s.position - newPosition).distance < horizontalGap && s.status != SpaceStatus.deleted)) {
s.status != SpaceStatus.deleted)) {
newPosition += Offset(horizontalGap, 0); newPosition += Offset(horizontalGap, 0);
} }
final duplicatedName = final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces);
SpaceHelper.generateUniqueSpaceName(original.name, spaces);
final List<SubspaceModel>? duplicatedSubspaces; final List<SubspaceModel>? duplicatedSubspaces;
final List<Tag>? duplicatedTags; final List<Tag>? duplicatedTags;
@ -681,8 +660,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
if (original.parent != null && duplicatedParent == null) { if (original.parent != null && duplicatedParent == null) {
final originalParent = original.parent!; final originalParent = original.parent!;
final duplicatedParent = final duplicatedParent = originalToDuplicate[originalParent] ?? originalParent;
originalToDuplicate[originalParent] ?? originalParent;
final parentConnection = Connection( final parentConnection = Connection(
startSpace: duplicatedParent, startSpace: duplicatedParent,
@ -698,8 +676,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
final childrenWithDownDirection = original.children final childrenWithDownDirection = original.children
.where((child) => .where((child) =>
child.incomingConnection?.direction == "down" && child.incomingConnection?.direction == "down" && child.status != SpaceStatus.deleted)
child.status != SpaceStatus.deleted)
.toList(); .toList();
Offset childStartPosition = childrenWithDownDirection.length == 1 Offset childStartPosition = childrenWithDownDirection.length == 1
@ -707,8 +684,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
: newPosition + Offset(0, verticalGap); : newPosition + Offset(0, verticalGap);
for (final child in original.children) { for (final child in original.children) {
final isDownDirection = final isDownDirection = child.incomingConnection?.direction == "down" ?? false;
child.incomingConnection?.direction == "down" ?? false;
if (isDownDirection && childrenWithDownDirection.length == 1) { if (isDownDirection && childrenWithDownDirection.length == 1) {
childStartPosition = duplicated.position + Offset(0, verticalGap); childStartPosition = duplicated.position + Offset(0, verticalGap);
@ -716,8 +692,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
childStartPosition = duplicated.position + Offset(horizontalGap, 0); childStartPosition = duplicated.position + Offset(horizontalGap, 0);
} }
final duplicatedChild = final duplicatedChild = duplicateRecursive(child, childStartPosition, duplicated);
duplicateRecursive(child, childStartPosition, duplicated);
duplicated.children.add(duplicatedChild); duplicated.children.add(duplicatedChild);
childStartPosition += Offset(0, verticalGap); childStartPosition += Offset(0, verticalGap);
} }
@ -728,8 +703,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
if (space.parent == null) { if (space.parent == null) {
duplicateRecursive(space, space.position, null); duplicateRecursive(space, space.position, null);
} else { } else {
final duplicatedParent = final duplicatedParent = originalToDuplicate[space.parent!] ?? space.parent!;
originalToDuplicate[space.parent!] ?? space.parent!;
duplicateRecursive(space, space.position, duplicatedParent); duplicateRecursive(space, space.position, duplicatedParent);
} }
} }

View File

@ -22,7 +22,7 @@ class CommunityTile extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: const EdgeInsets.only(left: 16.0), padding: const EdgeInsets.all(8.0),
child: CustomExpansionTile( child: CustomExpansionTile(
title: title, title: title,
initiallyExpanded: isExpanded, initiallyExpanded: isExpanded,

View File

@ -42,6 +42,7 @@ class CreateSpaceDialog extends StatefulWidget {
final List<Tag>? tags; final List<Tag>? tags;
final List<String>? allTags; final List<String>? allTags;
final SpaceTemplateModel? currentSpaceModel; final SpaceTemplateModel? currentSpaceModel;
final List<Tag> projectTags;
const CreateSpaceDialog( const CreateSpaceDialog(
{super.key, {super.key,
@ -57,7 +58,8 @@ class CreateSpaceDialog extends StatefulWidget {
this.spaceModels, this.spaceModels,
this.subspaces, this.subspaces,
this.tags, this.tags,
this.currentSpaceModel}); this.currentSpaceModel,
required this.projectTags});
@override @override
CreateSpaceDialogState createState() => CreateSpaceDialogState(); CreateSpaceDialogState createState() => CreateSpaceDialogState();
@ -80,10 +82,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
super.initState(); super.initState();
selectedIcon = widget.icon ?? Assets.location; selectedIcon = widget.icon ?? Assets.location;
nameController = TextEditingController(text: widget.name ?? ''); nameController = TextEditingController(text: widget.name ?? '');
selectedProducts = selectedProducts = widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
widget.selectedProducts.isNotEmpty ? widget.selectedProducts : []; isOkButtonEnabled = enteredName.isNotEmpty || nameController.text.isNotEmpty;
isOkButtonEnabled =
enteredName.isNotEmpty || nameController.text.isNotEmpty;
if (widget.currentSpaceModel != null) { if (widget.currentSpaceModel != null) {
subspaces = []; subspaces = [];
tags = []; tags = [];
@ -96,15 +96,13 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool isSpaceModelDisabled = (tags != null && tags!.isNotEmpty || bool isSpaceModelDisabled =
subspaces != null && subspaces!.isNotEmpty); (tags != null && tags!.isNotEmpty || subspaces != null && subspaces!.isNotEmpty);
bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null); bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null);
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
return AlertDialog( return AlertDialog(
title: widget.isEdit title: widget.isEdit ? const Text('Edit Space') : const Text('Create New Space'),
? const Text('Edit Space')
: const Text('Create New Space'),
backgroundColor: ColorsManager.whiteColors, backgroundColor: ColorsManager.whiteColors,
content: SizedBox( content: SizedBox(
width: screenWidth * 0.5, width: screenWidth * 0.5,
@ -178,7 +176,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
isNameFieldInvalid = value.isEmpty; isNameFieldInvalid = value.isEmpty;
if (!isNameFieldInvalid) { if (!isNameFieldInvalid) {
if (SpaceHelper.isNameConflict(value, widget.parentSpace, widget.editSpace)) { if (SpaceHelper.isNameConflict(
value, widget.parentSpace, widget.editSpace)) {
isNameFieldExist = true; isNameFieldExist = true;
isOkButtonEnabled = false; isOkButtonEnabled = false;
} else { } else {
@ -245,9 +244,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
), ),
onPressed: () { onPressed: () {
isSpaceModelDisabled isSpaceModelDisabled ? null : _showLinkSpaceModelDialog(context);
? null
: _showLinkSpaceModelDialog(context);
}, },
child: ButtonContentWidget( child: ButtonContentWidget(
svgAssets: Assets.link, svgAssets: Assets.link,
@ -257,8 +254,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
) )
: Container( : Container(
width: screenWidth * 0.25, width: screenWidth * 0.25,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
vertical: 10.0, horizontal: 16.0),
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorsManager.boxColor, color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
@ -273,8 +269,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyMedium! .bodyMedium!
.copyWith( .copyWith(color: ColorsManager.spaceColor),
color: ColorsManager.spaceColor),
), ),
backgroundColor: ColorsManager.whiteColors, backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -302,6 +297,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
), ),
onDeleted: () => setState(() { onDeleted: () => setState(() {
this.selectedSpaceModel = null; this.selectedSpaceModel = null;
subspaces = widget.subspaces ?? [];
tags = widget.tags ?? [];
})), })),
], ],
), ),
@ -343,8 +340,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
onPressed: () async { onPressed: () async {
isTagsAndSubspaceModelDisabled isTagsAndSubspaceModelDisabled
? null ? null
: _showSubSpaceDialog(context, enteredName, : _showSubSpaceDialog(
[], false, widget.products, subspaces); context, enteredName, [], false, widget.products, subspaces);
}, },
child: ButtonContentWidget( child: ButtonContentWidget(
icon: Icons.add, icon: Icons.add,
@ -371,22 +368,16 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
if (subspaces != null) if (subspaces != null)
...subspaces!.map((subspace) { ...subspaces!.map((subspace) {
return Column( return Column(
crossAxisAlignment: crossAxisAlignment: CrossAxisAlignment.start,
CrossAxisAlignment.start,
children: [ children: [
SubspaceNameDisplayWidget( SubspaceNameDisplayWidget(
text: subspace.subspaceName, text: subspace.subspaceName,
validateName: (updatedName) { validateName: (updatedName) {
bool nameExists = bool nameExists = subspaces!.any((s) {
subspaces!.any((s) { bool isSameId = s.internalId == subspace.internalId;
bool isSameId = s.internalId == bool isSameName =
subspace.internalId; s.subspaceName.trim().toLowerCase() ==
bool isSameName = s.subspaceName updatedName.trim().toLowerCase();
.trim()
.toLowerCase() ==
updatedName
.trim()
.toLowerCase();
return !isSameId && isSameName; return !isSameId && isSameName;
}); });
@ -395,8 +386,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
}, },
onNameChanged: (updatedName) { onNameChanged: (updatedName) {
setState(() { setState(() {
subspace.subspaceName = subspace.subspaceName = updatedName;
updatedName;
}); });
}, },
), ),
@ -405,8 +395,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
}), }),
EditChip( EditChip(
onTap: () async { onTap: () async {
_showSubSpaceDialog(context, enteredName, _showSubSpaceDialog(context, enteredName, [], true,
[], true, widget.products, subspaces); widget.products, subspaces);
}, },
) )
], ],
@ -415,9 +405,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
(tags?.isNotEmpty == true || (tags?.isNotEmpty == true ||
subspaces?.any((subspace) => subspaces?.any((subspace) => subspace.tags?.isNotEmpty == true) == true)
subspace.tags?.isNotEmpty == true) ==
true)
? SizedBox( ? SizedBox(
width: screenWidth * 0.25, width: screenWidth * 0.25,
child: Container( child: Container(
@ -437,16 +425,14 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
// Combine tags from spaceModel and subspaces // Combine tags from spaceModel and subspaces
...TagHelper.groupTags([ ...TagHelper.groupTags([
...?tags, ...?tags,
...?subspaces?.expand( ...?subspaces?.expand((subspace) => subspace.tags ?? [])
(subspace) => subspace.tags ?? [])
]).entries.map( ]).entries.map(
(entry) => Chip( (entry) => Chip(
avatar: SizedBox( avatar: SizedBox(
width: 24, width: 24,
height: 24, height: 24,
child: SvgPicture.asset( child: SvgPicture.asset(
entry.key.icon ?? entry.key.icon ?? 'assets/icons/gateway.svg',
'assets/icons/gateway.svg',
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
), ),
@ -455,15 +441,11 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodySmall .bodySmall
?.copyWith( ?.copyWith(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: const BorderSide( side: const BorderSide(
color: ColorsManager.spaceColor, color: ColorsManager.spaceColor,
), ),
@ -472,23 +454,21 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
), ),
EditChip(onTap: () async { EditChip(onTap: () async {
final result = await showDialog( await showDialog(
context: context, context: context,
builder: (context) => AssignTagDialog( builder: (context) => AssignTagDialog(
products: widget.products, products: widget.products,
subspaces: subspaces, subspaces: subspaces,
allTags: widget.allTags, allTags: widget.allTags,
addedProducts: TagHelper addedProducts:
.createInitialSelectedProductsForTags( TagHelper.createInitialSelectedProductsForTags(
tags ?? [], subspaces), tags ?? [], subspaces),
title: 'Edit Device', title: 'Edit Device',
initialTags: initialTags: TagHelper.generateInitialForTags(
TagHelper.generateInitialForTags( spaceTags: tags, subspaces: subspaces),
spaceTags: tags,
subspaces: subspaces),
spaceName: widget.name ?? '', spaceName: widget.name ?? '',
onSave: projectTags: widget.projectTags,
(updatedTags, updatedSubspaces) { onSave: (updatedTags, updatedSubspaces) {
setState(() { setState(() {
tags = updatedTags; tags = updatedTags;
subspaces = updatedSubspaces; subspaces = updatedSubspaces;
@ -547,25 +527,17 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
}); });
return; return;
} else { } else {
String newName = enteredName.isNotEmpty String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? '');
? enteredName
: (widget.name ?? '');
if (newName.isNotEmpty) { if (newName.isNotEmpty) {
widget.onCreateSpace( widget.onCreateSpace(newName, selectedIcon, selectedProducts,
newName, selectedSpaceModel, subspaces, tags);
selectedIcon,
selectedProducts,
selectedSpaceModel,
subspaces,
tags);
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
} }
}, },
borderRadius: 10, borderRadius: 10,
backgroundColor: isOkButtonEnabled backgroundColor:
? ColorsManager.secondaryColor isOkButtonEnabled ? ColorsManager.secondaryColor : ColorsManager.grayColor,
: ColorsManager.grayColor,
foregroundColor: ColorsManager.whiteColors, foregroundColor: ColorsManager.whiteColors,
child: const Text('OK'), child: const Text('OK'),
), ),
@ -592,7 +564,6 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
); );
} }
void _showLinkSpaceModelDialog(BuildContext context) { void _showLinkSpaceModelDialog(BuildContext context) {
showDialog( showDialog(
context: context, context: context,
@ -613,13 +584,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
); );
} }
void _showSubSpaceDialog( void _showSubSpaceDialog(BuildContext context, String name, final List<Tag>? spaceTags,
BuildContext context, bool isEdit, List<ProductModel>? products, final List<SubspaceModel>? existingSubSpaces) {
String name,
final List<Tag>? spaceTags,
bool isEdit,
List<ProductModel>? products,
final List<SubspaceModel>? existingSubSpaces) {
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
@ -634,12 +600,10 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
final List<Tag> tagsToAppendToSpace = []; final List<Tag> tagsToAppendToSpace = [];
if (slectedSubspaces != null) { if (slectedSubspaces != null) {
final updatedIds = final updatedIds = slectedSubspaces.map((s) => s.internalId).toSet();
slectedSubspaces.map((s) => s.internalId).toSet();
if (existingSubSpaces != null) { if (existingSubSpaces != null) {
final deletedSubspaces = existingSubSpaces final deletedSubspaces =
.where((s) => !updatedIds.contains(s.internalId)) existingSubSpaces.where((s) => !updatedIds.contains(s.internalId)).toList();
.toList();
for (var s in deletedSubspaces) { for (var s in deletedSubspaces) {
if (s.tags != null) { if (s.tags != null) {
tagsToAppendToSpace.addAll(s.tags!); tagsToAppendToSpace.addAll(s.tags!);
@ -659,20 +623,20 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
); );
} }
void _showTagCreateDialog(BuildContext context, String name, bool isEdit, void _showTagCreateDialog(
List<ProductModel>? products) { BuildContext context, String name, bool isEdit, List<ProductModel>? products) {
isEdit isEdit
? showDialog( ? showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return AssignTagDialog( return AssignTagDialog(
title: 'Edit Device', title: 'Edit Device',
addedProducts: TagHelper.createInitialSelectedProductsForTags( addedProducts: TagHelper.createInitialSelectedProductsForTags(tags, subspaces),
tags, subspaces),
spaceName: name, spaceName: name,
products: products, products: products,
subspaces: subspaces, subspaces: subspaces,
allTags: widget.allTags, allTags: widget.allTags,
projectTags: widget.projectTags,
onSave: (selectedSpaceTags, selectedSubspaces) { onSave: (selectedSpaceTags, selectedSubspaces) {
setState(() { setState(() {
tags = selectedSpaceTags; tags = selectedSpaceTags;
@ -682,8 +646,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
if (subspaces != null) { if (subspaces != null) {
for (final subspace in subspaces!) { for (final subspace in subspaces!) {
for (final selectedSubspace in selectedSubspaces) { for (final selectedSubspace in selectedSubspaces) {
if (subspace.subspaceName == if (subspace.subspaceName == selectedSubspace.subspaceName) {
selectedSubspace.subspaceName) {
subspace.tags = selectedSubspace.tags; subspace.tags = selectedSubspace.tags;
} }
} }
@ -705,9 +668,9 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
spaceTags: tags, spaceTags: tags,
isCreate: true, isCreate: true,
allTags: widget.allTags, allTags: widget.allTags,
projectTags: widget.projectTags,
initialSelectedProducts: initialSelectedProducts:
TagHelper.createInitialSelectedProductsForTags( TagHelper.createInitialSelectedProductsForTags(tags, subspaces),
tags, subspaces),
onSave: (selectedSpaceTags, selectedSubspaces) { onSave: (selectedSpaceTags, selectedSubspaces) {
setState(() { setState(() {
tags = selectedSpaceTags; tags = selectedSpaceTags;
@ -717,8 +680,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
if (subspaces != null) { if (subspaces != null) {
for (final subspace in subspaces!) { for (final subspace in subspaces!) {
for (final selectedSubspace in selectedSubspaces) { for (final selectedSubspace in selectedSubspaces) {
if (subspace.subspaceName == if (subspace.subspaceName == selectedSubspace.subspaceName) {
selectedSubspace.subspaceName) {
subspace.tags = selectedSubspace.tags; subspace.tags = selectedSubspace.tags;
} }
} }

View File

@ -1,9 +1,11 @@
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/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; import 'package:syncrow_web/pages/space_tree/view/space_tree_view.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/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/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/gradient_canvas_border_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/gradient_canvas_border_widget.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart';
@ -19,16 +21,17 @@ class LoadedSpaceView extends StatefulWidget {
final List<ProductModel>? products; final List<ProductModel>? products;
final List<SpaceTemplateModel>? spaceModels; final List<SpaceTemplateModel>? spaceModels;
final bool shouldNavigateToSpaceModelPage; final bool shouldNavigateToSpaceModelPage;
final List<Tag> projectTags;
const LoadedSpaceView({ const LoadedSpaceView(
super.key, {super.key,
required this.communities, required this.communities,
this.selectedCommunity, this.selectedCommunity,
this.selectedSpace, this.selectedSpace,
this.products, this.products,
this.spaceModels, this.spaceModels,
required this.shouldNavigateToSpaceModelPage, required this.shouldNavigateToSpaceModelPage,
}); required this.projectTags});
@override @override
_LoadedSpaceViewState createState() => _LoadedSpaceViewState(); _LoadedSpaceViewState createState() => _LoadedSpaceViewState();
@ -78,26 +81,25 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
clipBehavior: Clip.none, clipBehavior: Clip.none,
children: [ children: [
widget.shouldNavigateToSpaceModelPage widget.shouldNavigateToSpaceModelPage
? _spaceModels.isNotEmpty ? Row(
? Row( children: [
children: [ SizedBox(width: 300, child: SpaceTreeView(onSelect: () {})),
SizedBox( Expanded(
width: 300, child: SpaceTreeView(onSelect: () {})), child: BlocProvider(
Expanded( create: (context) => SpaceModelBloc(
child: BlocProvider( BlocProvider.of<SpaceTreeBloc>(context),
create: (context) => SpaceModelBloc( api: SpaceModelManagementApi(),
api: SpaceModelManagementApi(), initialSpaceModels: widget.spaceModels ?? [],
initialSpaceModels: _spaceModels,
),
child: SpaceModelPage(
products: widget.products,
onSpaceModelsUpdated: _onSpaceModelsUpdated,
),
),
), ),
], child: SpaceModelPage(
) products: widget.products,
: const Center(child: CircularProgressIndicator()) onSpaceModelsUpdated: _onSpaceModelsUpdated,
projectTags: widget.projectTags,
),
),
),
],
)
: Row( : Row(
children: [ children: [
SidebarWidget( SidebarWidget(
@ -113,6 +115,7 @@ class _LoadedSpaceViewState extends State<LoadedSpaceView> {
products: widget.products, products: widget.products,
communities: widget.communities, communities: widget.communities,
spaceModels: _spaceModels, spaceModels: _spaceModels,
projectTags: widget.projectTags,
), ),
], ],
), ),

View File

@ -205,30 +205,32 @@ class _SidebarWidgetState extends State<SidebarWidget> {
); );
} }
Widget _buildSpaceTile(SpaceModel space, CommunityModel community) { Widget _buildSpaceTile(SpaceModel space, CommunityModel community, {int depth = 1}) {
bool isExpandedSpace = _isSpaceOrChildSelected(space); bool isExpandedSpace = _isSpaceOrChildSelected(space);
return SpaceTile( return Padding(
title: space.name, padding: EdgeInsets.only(left: depth * 16.0),
key: ValueKey(space.uuid), child: SpaceTile(
isSelected: _selectedId == space.uuid, title: space.name,
initiallyExpanded: isExpandedSpace, key: ValueKey(space.uuid),
onExpansionChanged: (bool expanded) { isSelected: _selectedId == space.uuid,
_handleExpansionChange(space.uuid ?? '', expanded); initiallyExpanded: isExpandedSpace,
}, onExpansionChanged: (bool expanded) {
onItemSelected: () { _handleExpansionChange(space.uuid ?? '', expanded);
setState(() { },
_selectedId = space.uuid; onItemSelected: () {
_selectedSpaceUuid = space.uuid; setState(() {
}); _selectedId = space.uuid;
_selectedSpaceUuid = space.uuid;
});
context.read<SpaceManagementBloc>().add( context.read<SpaceManagementBloc>().add(
SelectSpaceEvent(selectedCommunity: community, selectedSpace: space), SelectSpaceEvent(selectedCommunity: community, selectedSpace: space),
); );
}, },
children: space.children.isNotEmpty children: space.children.isNotEmpty
? space.children.map((childSpace) => _buildSpaceTile(childSpace, community)).toList() ? space.children.map((childSpace) => _buildSpaceTile(childSpace, community)).toList()
: [], // Recursively render child spaces if available : [], // Recursively render child spaces if available
); ));
} }
void _handleExpansionChange(String uuid, bool expanded) {} void _handleExpansionChange(String uuid, bool expanded) {}

View File

@ -35,18 +35,20 @@ class _SpaceTileState extends State<SpaceTile> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CustomExpansionTile( return Padding(
isSelected: widget.isSelected, padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 8.0),
title: widget.title, child: CustomExpansionTile(
initiallyExpanded: _isExpanded, isSelected: widget.isSelected,
onItemSelected: widget.onItemSelected, title: widget.title,
onExpansionChanged: (bool expanded) { initiallyExpanded: _isExpanded,
setState(() { onItemSelected: widget.onItemSelected,
_isExpanded = expanded; onExpansionChanged: (bool expanded) {
}); setState(() {
widget.onExpansionChanged(expanded); _isExpanded = expanded;
}, });
children: widget.children ?? [], widget.onExpansionChanged(expanded);
); },
children: widget.children ?? [],
));
} }
} }

View File

@ -4,17 +4,16 @@ import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_e
import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart';
class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> { class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final List<String> allTags; final List<Tag> projectTags;
AssignTagBloc(this.allTags) : super(AssignTagInitial()) { AssignTagBloc(this.projectTags) : super(AssignTagInitial()) {
on<InitializeTags>((event, emit) { on<InitializeTags>((event, emit) {
final initialTags = event.initialTags ?? []; final initialTags = event.initialTags ?? [];
final existingTagCounts = <String, int>{}; final existingTagCounts = <String, int>{};
for (var tag in initialTags) { for (var tag in initialTags) {
if (tag.product != null) { if (tag.product != null) {
existingTagCounts[tag.product!.uuid] = existingTagCounts[tag.product!.uuid] = (existingTagCounts[tag.product!.uuid] ?? 0) + 1;
(existingTagCounts[tag.product!.uuid] ?? 0) + 1;
} }
} }
@ -23,17 +22,14 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
for (var selectedProduct in event.addedProducts) { for (var selectedProduct in event.addedProducts) {
final existingCount = existingTagCounts[selectedProduct.productId] ?? 0; final existingCount = existingTagCounts[selectedProduct.productId] ?? 0;
if (selectedProduct.count == 0 || if (selectedProduct.count == 0 || selectedProduct.count <= existingCount) {
selectedProduct.count <= existingCount) { tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
continue; continue;
} }
final missingCount = selectedProduct.count - existingCount; final missingCount = selectedProduct.count - existingCount;
tags.addAll(initialTags tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
.where((tag) => tag.product?.uuid == selectedProduct.productId));
if (missingCount > 0) { if (missingCount > 0) {
tags.addAll(List.generate( tags.addAll(List.generate(
@ -47,11 +43,11 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
} }
} }
final updatedTags = _calculateAvailableTags(allTags, tags); final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: tags, tags: tags,
updatedTags: updatedTags, updatedTags: updatedTags,
isSaveEnabled: _validateTags(tags), isSaveEnabled: _validateTags(tags),
errorMessage: '', errorMessage: '',
)); ));
@ -62,9 +58,16 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags); final tags = List<Tag>.from(currentState.tags);
tags[event.index] = tags[event.index].copyWith(tag: event.tag); if (event.index < 0 || event.index >= tags.length) return;
final updatedTags = _calculateAvailableTags(allTags, tags); tags[event.index] = tags[event.index].copyWith(
tag: event.tag.tag,
uuid: event.tag.uuid,
product: event.tag.product,
internalId: event.tag.internalId,
location: event.tag.location,
);
final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: tags, tags: tags,
@ -82,10 +85,9 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final tags = List<Tag>.from(currentState.tags); final tags = List<Tag>.from(currentState.tags);
// Update the location // Update the location
tags[event.index] = tags[event.index] = tags[event.index].copyWith(location: event.location);
tags[event.index].copyWith(location: event.location);
final updatedTags = _calculateAvailableTags(allTags, tags); final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: tags, tags: tags,
@ -104,7 +106,7 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: tags, tags: tags,
updatedTags: _calculateAvailableTags(allTags, tags), updatedTags: _calculateAvailableTags(projectTags, tags),
isSaveEnabled: _validateTags(tags), isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags), errorMessage: _getValidationError(tags),
)); ));
@ -115,11 +117,10 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
final currentState = state; final currentState = state;
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) { if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags) final tags = List<Tag>.from(currentState.tags)..remove(event.tagToDelete);
..remove(event.tagToDelete);
// Recalculate available tags // Recalculate available tags
final updatedTags = _calculateAvailableTags(allTags, tags); final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagLoaded( emit(AssignTagLoaded(
tags: tags, tags: tags,
@ -140,10 +141,8 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
// Get validation error for duplicate tags // Get validation error for duplicate tags
String? _getValidationError(List<Tag> tags) { String? _getValidationError(List<Tag> tags) {
final nonEmptyTags = tags final nonEmptyTags =
.map((tag) => tag.tag?.trim() ?? '') tags.map((tag) => tag.tag?.trim() ?? '').where((tag) => tag.isNotEmpty).toList();
.where((tag) => tag.isNotEmpty)
.toList();
final duplicateTags = nonEmptyTags final duplicateTags = nonEmptyTags
.fold<Map<String, int>>({}, (map, tag) { .fold<Map<String, int>>({}, (map, tag) {
@ -162,14 +161,16 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
return null; return null;
} }
List<String> _calculateAvailableTags(List<String> allTags, List<Tag> tags) { List<Tag> _calculateAvailableTags(List<Tag> allTags, List<Tag> selectedTags) {
final selectedTags = tags final selectedTagSet = selectedTags
.where((tag) => (tag.tag?.trim().isNotEmpty ?? false)) .where((tag) => (tag.tag?.trim().isNotEmpty ?? false))
.map((tag) => tag.tag!.trim()) .map((tag) => tag.tag!.trim())
.toSet(); .toSet();
final availableTags = final availableTags = allTags
allTags.where((tag) => !selectedTags.contains(tag.trim())).toList(); .where((tag) => tag.tag != null && !selectedTagSet.contains(tag.tag!.trim()))
.toList();
return availableTags; return availableTags;
} }
} }

View File

@ -24,7 +24,7 @@ class InitializeTags extends AssignTagEvent {
class UpdateTagEvent extends AssignTagEvent { class UpdateTagEvent extends AssignTagEvent {
final int index; final int index;
final String tag; final Tag tag;
const UpdateTagEvent({required this.index, required this.tag}); const UpdateTagEvent({required this.index, required this.tag});

View File

@ -5,7 +5,7 @@ abstract class AssignTagState extends Equatable {
const AssignTagState(); const AssignTagState();
@override @override
List<Object> get props => []; List<Object?> get props => [];
} }
class AssignTagInitial extends AssignTagState {} class AssignTagInitial extends AssignTagState {}
@ -14,7 +14,7 @@ class AssignTagLoading extends AssignTagState {}
class AssignTagLoaded extends AssignTagState { class AssignTagLoaded extends AssignTagState {
final List<Tag> tags; final List<Tag> tags;
final List<String> updatedTags; final List<Tag> updatedTags;
final bool isSaveEnabled; final bool isSaveEnabled;
final String? errorMessage; final String? errorMessage;
@ -27,8 +27,7 @@ class AssignTagLoaded extends AssignTagState {
}); });
@override @override
List<Object> get props => List<Object?> get props => [tags, updatedTags, isSaveEnabled, errorMessage ?? ''];
[tags, updatedTags, isSaveEnabled, errorMessage ?? ''];
} }
class AssignTagError extends AssignTagState { class AssignTagError extends AssignTagState {
@ -37,5 +36,5 @@ class AssignTagError extends AssignTagState {
const AssignTagError(this.errorMessage); const AssignTagError(this.errorMessage);
@override @override
List<Object> get props => [errorMessage]; List<Object?> get props => [errorMessage];
} }

View File

@ -1,7 +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_dropdown.dart';
import 'package:syncrow_web/common/dialog_textfield_dropdown.dart'; import 'package:syncrow_web/common/tag_dialog_textfield_dropdown.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/add_device_type/views/add_device_type_widget.dart';
@ -26,6 +26,7 @@ class AssignTagDialog extends StatelessWidget {
final String spaceName; final String spaceName;
final String title; final String title;
final Function(List<Tag>, List<SubspaceModel>?)? onSave; final Function(List<Tag>, List<SubspaceModel>?)? onSave;
final List<Tag> projectTags;
const AssignTagDialog( const AssignTagDialog(
{Key? key, {Key? key,
@ -37,18 +38,17 @@ class AssignTagDialog extends StatelessWidget {
this.allTags, this.allTags,
required this.spaceName, required this.spaceName,
required this.title, required this.title,
this.onSave}) this.onSave,
required this.projectTags})
: super(key: key); : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<String> locations = (subspaces ?? []) final List<String> locations =
.map((subspace) => subspace.subspaceName) (subspaces ?? []).map((subspace) => subspace.subspaceName).toList()..add('Main Space');
.toList()
..add('Main Space');
return BlocProvider( return BlocProvider(
create: (_) => AssignTagBloc(allTags ?? []) create: (_) => AssignTagBloc(projectTags)
..add(InitializeTags( ..add(InitializeTags(
initialTags: initialTags, initialTags: initialTags,
addedProducts: addedProducts, addedProducts: addedProducts,
@ -70,8 +70,7 @@ class AssignTagDialog extends StatelessWidget {
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
child: DataTable( child: DataTable(
headingRowColor: WidgetStateProperty.all( headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey),
ColorsManager.dataHeaderGrey),
key: ValueKey(state.tags.length), key: ValueKey(state.tags.length),
border: TableBorder.all( border: TableBorder.all(
color: ColorsManager.dataHeaderGrey, color: ColorsManager.dataHeaderGrey,
@ -80,22 +79,15 @@ class AssignTagDialog extends StatelessWidget {
), ),
columns: [ columns: [
DataColumn( DataColumn(
label: Text('#', label: Text('#', style: Theme.of(context).textTheme.bodyMedium)),
style:
Theme.of(context).textTheme.bodyMedium)),
DataColumn( DataColumn(
label: Text('Device', label: Text('Device', style: Theme.of(context).textTheme.bodyMedium)),
style:
Theme.of(context).textTheme.bodyMedium)),
DataColumn( DataColumn(
numeric: false, numeric: false,
label: Text('Tag', label: Text('Tag', style: Theme.of(context).textTheme.bodyMedium)),
style:
Theme.of(context).textTheme.bodyMedium)),
DataColumn( DataColumn(
label: Text('Location', label:
style: Text('Location', style: Theme.of(context).textTheme.bodyMedium)),
Theme.of(context).textTheme.bodyMedium)),
], ],
rows: state.tags.isEmpty rows: state.tags.isEmpty
? [ ? [
@ -103,12 +95,8 @@ class AssignTagDialog extends StatelessWidget {
DataCell( DataCell(
Center( Center(
child: Text('No Data Available', child: Text('No Data Available',
style: Theme.of(context) style: Theme.of(context).textTheme.bodyMedium?.copyWith(
.textTheme color: ColorsManager.lightGrayColor,
.bodyMedium
?.copyWith(
color: ColorsManager
.lightGrayColor,
)), )),
), ),
), ),
@ -126,8 +114,7 @@ class AssignTagDialog extends StatelessWidget {
DataCell(Text((index + 1).toString())), DataCell(Text((index + 1).toString())),
DataCell( DataCell(
Row( Row(
mainAxisAlignment: mainAxisAlignment: MainAxisAlignment.spaceBetween,
MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
@ -141,31 +128,25 @@ class AssignTagDialog extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all( border: Border.all(
color: ColorsManager color: ColorsManager.lightGrayColor,
.lightGrayColor,
width: 1.0, width: 1.0,
), ),
), ),
child: IconButton( child: IconButton(
icon: const Icon( icon: const Icon(
Icons.close, Icons.close,
color: ColorsManager color: ColorsManager.lightGreyColor,
.lightGreyColor,
size: 16, size: 16,
), ),
onPressed: () { onPressed: () {
context context.read<AssignTagBloc>().add(
.read<AssignTagBloc>() DeleteTag(tagToDelete: tag, tags: state.tags));
.add(DeleteTag(
tagToDelete: tag,
tags: state.tags));
controllers.removeAt(index); controllers.removeAt(index);
}, },
tooltip: 'Delete Tag', tooltip: 'Delete Tag',
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
constraints: constraints: const BoxConstraints(),
const BoxConstraints(),
), ),
), ),
], ],
@ -173,23 +154,20 @@ class AssignTagDialog extends StatelessWidget {
), ),
DataCell( DataCell(
Container( Container(
alignment: Alignment alignment:
.centerLeft, // Align cell content to the left Alignment.centerLeft, // Align cell content to the left
child: SizedBox( child: SizedBox(
width: double width: double.infinity,
.infinity, // Ensure full width for dropdown child: TagDialogTextfieldDropdown(
child: DialogTextfieldDropdown( key: ValueKey('dropdown_${const Uuid().v4()}_$index'),
key: ValueKey(
'dropdown_${Uuid().v4()}_${index}'),
items: state.updatedTags, items: state.updatedTags,
initialValue: tag.tag, product: tag.product?.uuid ?? 'Unknown',
initialValue: tag,
onSelected: (value) { onSelected: (value) {
controller.text = value; controller.text = value.tag ?? '';
context context.read<AssignTagBloc>().add(UpdateTagEvent(
.read<AssignTagBloc>()
.add(UpdateTagEvent(
index: index, index: index,
tag: value.trim(), tag: value,
)); ));
}, },
), ),
@ -201,12 +179,9 @@ class AssignTagDialog extends StatelessWidget {
width: double.infinity, width: double.infinity,
child: DialogDropdown( child: DialogDropdown(
items: locations, items: locations,
selectedValue: selectedValue: tag.location ?? 'Main Space',
tag.location ?? 'Main Space',
onSelected: (value) { onSelected: (value) {
context context.read<AssignTagBloc>().add(UpdateLocation(
.read<AssignTagBloc>()
.add(UpdateLocation(
index: index, index: index,
location: value, location: value,
)); ));
@ -238,13 +213,11 @@ class AssignTagDialog extends StatelessWidget {
label: 'Add New Device', label: 'Add New Device',
onPressed: () async { onPressed: () async {
final updatedTags = List<Tag>.from(state.tags); final updatedTags = List<Tag>.from(state.tags);
final result = final result = TagHelper.processTags(updatedTags, subspaces);
TagHelper.processTags(updatedTags, subspaces);
final processedTags = final processedTags = result['updatedTags'] as List<Tag>;
result['updatedTags'] as List<Tag>; final processedSubspaces =
final processedSubspaces = List<SubspaceModel>.from( List<SubspaceModel>.from(result['subspaces'] as List<dynamic>);
result['subspaces'] as List<dynamic>);
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -253,8 +226,9 @@ class AssignTagDialog extends StatelessWidget {
builder: (context) => AddDeviceTypeWidget( builder: (context) => AddDeviceTypeWidget(
products: products, products: products,
subspaces: processedSubspaces, subspaces: processedSubspaces,
initialSelectedProducts: TagHelper projectTags: projectTags,
.createInitialSelectedProductsForTags( initialSelectedProducts:
TagHelper.createInitialSelectedProductsForTags(
processedTags, processedSubspaces), processedTags, processedSubspaces),
spaceName: spaceName, spaceName: spaceName,
spaceTags: processedTags, spaceTags: processedTags,
@ -278,14 +252,11 @@ class AssignTagDialog extends StatelessWidget {
onPressed: state.isSaveEnabled onPressed: state.isSaveEnabled
? () async { ? () async {
final updatedTags = List<Tag>.from(state.tags); final updatedTags = List<Tag>.from(state.tags);
final result = TagHelper.processTags( final result = TagHelper.processTags(updatedTags, subspaces);
updatedTags, subspaces);
final processedTags = final processedTags = result['updatedTags'] as List<Tag>;
result['updatedTags'] as List<Tag>;
final processedSubspaces = final processedSubspaces =
List<SubspaceModel>.from( List<SubspaceModel>.from(result['subspaces'] as List<dynamic>);
result['subspaces'] as List<dynamic>);
onSave?.call(processedTags, processedSubspaces); onSave?.call(processedTags, processedSubspaces);
Navigator.of(context).pop(); Navigator.of(context).pop();
} }

View File

@ -1,45 +1,40 @@
import 'package:flutter_bloc/flutter_bloc.dart'; 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_models/bloc/assign_tag_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
class AssignTagModelBloc class AssignTagModelBloc extends Bloc<AssignTagModelEvent, AssignTagModelState> {
extends Bloc<AssignTagModelEvent, AssignTagModelState> { final List<Tag> projectTags;
final List<String> allTags;
AssignTagModelBloc(this.allTags) : super(AssignTagModelInitial()) { AssignTagModelBloc(this.projectTags) : super(AssignTagModelInitial()) {
on<InitializeTagModels>((event, emit) { on<InitializeTagModels>((event, emit) {
final initialTags = event.initialTags ?? []; final initialTags = event.initialTags;
final existingTagCounts = <String, int>{}; final existingTagCounts = <String, int>{};
for (var tag in initialTags) { for (var tag in initialTags) {
if (tag.product != null) { if (tag.product != null) {
existingTagCounts[tag.product!.uuid] = existingTagCounts[tag.product!.uuid] = (existingTagCounts[tag.product!.uuid] ?? 0) + 1;
(existingTagCounts[tag.product!.uuid] ?? 0) + 1;
} }
} }
final tags = <TagModel>[]; final tags = <Tag>[];
for (var selectedProduct in event.addedProducts) { for (var selectedProduct in event.addedProducts) {
final existingCount = existingTagCounts[selectedProduct.productId] ?? 0; final existingCount = existingTagCounts[selectedProduct.productId] ?? 0;
if (selectedProduct.count == 0 || if (selectedProduct.count == 0 || selectedProduct.count <= existingCount) {
selectedProduct.count <= existingCount) { tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
tags.addAll(initialTags
.where((tag) => tag.product?.uuid == selectedProduct.productId));
continue; continue;
} }
final missingCount = selectedProduct.count - existingCount; final missingCount = selectedProduct.count - existingCount;
tags.addAll(initialTags tags.addAll(initialTags.where((tag) => tag.product?.uuid == selectedProduct.productId));
.where((tag) => tag.product?.uuid == selectedProduct.productId));
if (missingCount > 0) { if (missingCount > 0) {
tags.addAll(List.generate( tags.addAll(List.generate(
missingCount, missingCount,
(index) => TagModel( (index) => Tag(
tag: '', tag: '',
product: selectedProduct.product, product: selectedProduct.product,
location: 'Main Space', location: 'Main Space',
@ -48,7 +43,7 @@ class AssignTagModelBloc
} }
} }
final updatedTags = _calculateAvailableTags(allTags, tags); final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagModelLoaded( emit(AssignTagModelLoaded(
tags: tags, tags: tags,
@ -59,11 +54,20 @@ class AssignTagModelBloc
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<Tag>.from(currentState.tags);
final tags = List<TagModel>.from(currentState.tags);
tags[event.index] = tags[event.index].copyWith(tag: event.tag); if (event.index < 0 || event.index >= tags.length) return;
final updatedTags = _calculateAvailableTags(allTags, tags);
tags[event.index] = tags[event.index].copyWith(
tag: event.tag.tag,
uuid: event.tag.uuid,
product: event.tag.product,
internalId: event.tag.internalId,
location: event.tag.location,
);
final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagModelLoaded( emit(AssignTagModelLoaded(
tags: tags, tags: tags,
@ -77,15 +81,12 @@ class AssignTagModelBloc
on<UpdateLocation>((event, emit) { on<UpdateLocation>((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<Tag>.from(currentState.tags);
final tags = List<TagModel>.from(currentState.tags);
// Use copyWith for immutability // Use copyWith for immutability
tags[event.index] = tags[event.index] = tags[event.index].copyWith(location: event.location);
tags[event.index].copyWith(location: event.location);
final updatedTags = _calculateAvailableTags(allTags, tags); final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagModelLoaded( emit(AssignTagModelLoaded(
tags: tags, tags: tags,
@ -99,13 +100,12 @@ class AssignTagModelBloc
on<ValidateTagModels>((event, emit) { on<ValidateTagModels>((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<Tag>.from(currentState.tags);
final tags = List<TagModel>.from(currentState.tags);
emit(AssignTagModelLoaded( emit(AssignTagModelLoaded(
tags: tags, tags: tags,
updatedTags: _calculateAvailableTags(allTags, tags), updatedTags: _calculateAvailableTags(projectTags, tags),
isSaveEnabled: _validateTags(tags), isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags), errorMessage: _getValidationError(tags),
)); ));
@ -115,12 +115,10 @@ class AssignTagModelBloc
on<DeleteTagModel>((event, emit) { on<DeleteTagModel>((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<Tag>.from(currentState.tags)..remove(event.tagToDelete);
final tags = List<TagModel>.from(currentState.tags)
..remove(event.tagToDelete);
final updatedTags = _calculateAvailableTags(allTags, tags); final updatedTags = _calculateAvailableTags(projectTags, tags);
emit(AssignTagModelLoaded( emit(AssignTagModelLoaded(
tags: tags, tags: tags,
@ -128,24 +126,22 @@ class AssignTagModelBloc
isSaveEnabled: _validateTags(tags), isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags), errorMessage: _getValidationError(tags),
)); ));
} }
}); });
} }
bool _validateTags(List<TagModel> tags) { bool _validateTags(List<Tag> tags) {
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);
final isValid = uniqueTags.length == tags.length && !hasEmptyTag; final isValid = uniqueTags.length == tags.length && !hasEmptyTag;
return isValid; return isValid;
} }
String? _getValidationError(List<TagModel> tags) { String? _getValidationError(List<Tag> tags) {
// Check for duplicate tags // Check for duplicate tags
final nonEmptyTags = tags final nonEmptyTags =
.map((tag) => tag.tag?.trim() ?? '') tags.map((tag) => tag.tag?.trim() ?? '').where((tag) => tag.isNotEmpty).toList();
.where((tag) => tag.isNotEmpty)
.toList();
final duplicateTags = nonEmptyTags final duplicateTags = nonEmptyTags
.fold<Map<String, int>>({}, (map, tag) { .fold<Map<String, int>>({}, (map, tag) {
@ -164,15 +160,16 @@ class AssignTagModelBloc
return null; return null;
} }
List<String> _calculateAvailableTags( List<Tag> _calculateAvailableTags(List<Tag> allTags, List<Tag> selectedTags) {
List<String> allTags, List<TagModel> tags) { final selectedTagSet = selectedTags
final selectedTags = tags
.where((tag) => (tag.tag?.trim().isNotEmpty ?? false)) .where((tag) => (tag.tag?.trim().isNotEmpty ?? false))
.map((tag) => tag.tag!.trim()) .map((tag) => tag.tag!.trim())
.toSet(); .toSet();
final availableTags = final availableTags = allTags
allTags.where((tag) => !selectedTags.contains(tag.trim())).toList(); .where((tag) => tag.tag != null && !selectedTagSet.contains(tag.tag!.trim()))
.toList();
return availableTags; return availableTags;
} }
} }

View File

@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.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';
abstract class AssignTagModelEvent extends Equatable { abstract class AssignTagModelEvent extends Equatable {
@ -10,7 +10,7 @@ abstract class AssignTagModelEvent extends Equatable {
} }
class InitializeTagModels extends AssignTagModelEvent { class InitializeTagModels extends AssignTagModelEvent {
final List<TagModel> initialTags; final List<Tag> initialTags;
final List<SelectedProduct> addedProducts; final List<SelectedProduct> addedProducts;
const InitializeTagModels({ const InitializeTagModels({
@ -24,7 +24,7 @@ class InitializeTagModels extends AssignTagModelEvent {
class UpdateTag extends AssignTagModelEvent { class UpdateTag extends AssignTagModelEvent {
final int index; final int index;
final String tag; final Tag tag;
const UpdateTag({required this.index, required this.tag}); const UpdateTag({required this.index, required this.tag});
@ -45,8 +45,8 @@ class UpdateLocation extends AssignTagModelEvent {
class ValidateTagModels extends AssignTagModelEvent {} class ValidateTagModels extends AssignTagModelEvent {}
class DeleteTagModel extends AssignTagModelEvent { class DeleteTagModel extends AssignTagModelEvent {
final TagModel tagToDelete; final Tag tagToDelete;
final List<TagModel> tags; final List<Tag> tags;
const DeleteTagModel({required this.tagToDelete, required this.tags}); const DeleteTagModel({required this.tagToDelete, required this.tags});

View File

@ -1,5 +1,5 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
abstract class AssignTagModelState extends Equatable { abstract class AssignTagModelState extends Equatable {
const AssignTagModelState(); const AssignTagModelState();
@ -13,11 +13,11 @@ class AssignTagModelInitial extends AssignTagModelState {}
class AssignTagModelLoading extends AssignTagModelState {} class AssignTagModelLoading extends AssignTagModelState {}
class AssignTagModelLoaded extends AssignTagModelState { class AssignTagModelLoaded extends AssignTagModelState {
final List<TagModel> tags; final List<Tag> tags;
final bool isSaveEnabled; final bool isSaveEnabled;
final String? errorMessage; final String? errorMessage;
final List<String> updatedTags; final List<Tag> updatedTags;
const AssignTagModelLoaded({ const AssignTagModelLoaded({
required this.tags, required this.tags,

View File

@ -1,17 +1,17 @@
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_dropdown.dart';
import 'package:syncrow_web/common/dialog_textfield_dropdown.dart'; import 'package:syncrow_web/common/tag_dialog_textfield_dropdown.dart';
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/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';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_event.dart';
import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/bloc/assign_tag_model_state.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/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/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';
@ -23,8 +23,8 @@ class AssignTagModelsDialog extends StatelessWidget {
final List<SubspaceTemplateModel>? subspaces; final List<SubspaceTemplateModel>? subspaces;
final SpaceTemplateModel? spaceModel; final SpaceTemplateModel? spaceModel;
final List<TagModel> initialTags; final List<Tag> initialTags;
final ValueChanged<List<TagModel>>? onTagsAssigned; final ValueChanged<List<Tag>>? onTagsAssigned;
final List<SelectedProduct> addedProducts; final List<SelectedProduct> addedProducts;
final List<String>? allTags; final List<String>? allTags;
final String spaceName; final String spaceName;
@ -32,6 +32,7 @@ class AssignTagModelsDialog extends StatelessWidget {
final BuildContext? pageContext; final BuildContext? pageContext;
final List<String>? otherSpaceModels; final List<String>? otherSpaceModels;
final List<SpaceTemplateModel>? allSpaceModels; final List<SpaceTemplateModel>? allSpaceModels;
final List<Tag> projectTags;
const AssignTagModelsDialog( const AssignTagModelsDialog(
{Key? key, {Key? key,
@ -46,18 +47,17 @@ class AssignTagModelsDialog extends StatelessWidget {
this.pageContext, this.pageContext,
this.otherSpaceModels, this.otherSpaceModels,
this.spaceModel, this.spaceModel,
this.allSpaceModels}) this.allSpaceModels,
required this.projectTags})
: super(key: key); : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<String> locations = (subspaces ?? []) final List<String> locations =
.map((subspace) => subspace.subspaceName) (subspaces ?? []).map((subspace) => subspace.subspaceName).toList()..add('Main Space');
.toList()
..add('Main Space');
return BlocProvider( return BlocProvider(
create: (_) => AssignTagModelBloc(allTags ?? []) create: (_) => AssignTagModelBloc(projectTags)
..add(InitializeTagModels( ..add(InitializeTagModels(
initialTags: initialTags, initialTags: initialTags,
addedProducts: addedProducts, addedProducts: addedProducts,
@ -81,8 +81,7 @@ class AssignTagModelsDialog extends StatelessWidget {
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
child: DataTable( child: DataTable(
headingRowColor: WidgetStateProperty.all( headingRowColor: WidgetStateProperty.all(ColorsManager.dataHeaderGrey),
ColorsManager.dataHeaderGrey),
key: ValueKey(state.tags.length), key: ValueKey(state.tags.length),
border: TableBorder.all( border: TableBorder.all(
color: ColorsManager.dataHeaderGrey, color: ColorsManager.dataHeaderGrey,
@ -91,26 +90,17 @@ class AssignTagModelsDialog extends StatelessWidget {
), ),
columns: [ columns: [
DataColumn( DataColumn(
label: Text('#', label: Text('#', style: Theme.of(context).textTheme.bodyMedium)),
style: Theme.of(context)
.textTheme
.bodyMedium)),
DataColumn( DataColumn(
label: Text('Device', label: Text('Device',
style: Theme.of(context) style: Theme.of(context).textTheme.bodyMedium)),
.textTheme
.bodyMedium)),
DataColumn( DataColumn(
numeric: false, numeric: false,
label: Text('Tag', label:
style: Theme.of(context) Text('Tag', style: Theme.of(context).textTheme.bodyMedium)),
.textTheme
.bodyMedium)),
DataColumn( DataColumn(
label: Text('Location', label: Text('Location',
style: Theme.of(context) style: Theme.of(context).textTheme.bodyMedium)),
.textTheme
.bodyMedium)),
], ],
rows: state.tags.isEmpty rows: state.tags.isEmpty
? [ ? [
@ -118,13 +108,10 @@ class AssignTagModelsDialog extends StatelessWidget {
DataCell( DataCell(
Center( Center(
child: Text('No Devices Available', child: Text('No Devices Available',
style: Theme.of(context) style:
.textTheme Theme.of(context).textTheme.bodyMedium?.copyWith(
.bodyMedium color: ColorsManager.lightGrayColor,
?.copyWith( )),
color: ColorsManager
.lightGrayColor,
)),
), ),
), ),
const DataCell(SizedBox()), const DataCell(SizedBox()),
@ -141,8 +128,7 @@ class AssignTagModelsDialog extends StatelessWidget {
DataCell(Text((index + 1).toString())), DataCell(Text((index + 1).toString())),
DataCell( DataCell(
Row( Row(
mainAxisAlignment: mainAxisAlignment: MainAxisAlignment.spaceBetween,
MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
@ -156,31 +142,25 @@ class AssignTagModelsDialog extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all( border: Border.all(
color: ColorsManager color: ColorsManager.lightGrayColor,
.lightGrayColor,
width: 1.0, width: 1.0,
), ),
), ),
child: IconButton( child: IconButton(
icon: const Icon( icon: const Icon(
Icons.close, Icons.close,
color: ColorsManager color: ColorsManager.lightGreyColor,
.lightGreyColor,
size: 16, size: 16,
), ),
onPressed: () { onPressed: () {
context context.read<AssignTagModelBloc>().add(
.read< DeleteTagModel(
AssignTagModelBloc>() tagToDelete: tag, tags: state.tags));
.add(DeleteTagModel(
tagToDelete: tag,
tags: state.tags));
controllers.removeAt(index); controllers.removeAt(index);
}, },
tooltip: 'Delete Tag', tooltip: 'Delete Tag',
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
constraints: constraints: const BoxConstraints(),
const BoxConstraints(),
), ),
), ),
], ],
@ -191,19 +171,16 @@ class AssignTagModelsDialog extends StatelessWidget {
alignment: Alignment alignment: Alignment
.centerLeft, // Align cell content to the left .centerLeft, // Align cell content to the left
child: SizedBox( child: SizedBox(
width: double width: double.infinity,
.infinity, // Ensure full width for dropdown child: TagDialogTextfieldDropdown(
child: DialogTextfieldDropdown( key: ValueKey(
key: ValueKey( 'dropdown_${const Uuid().v4()}_$index'),
'dropdown_${Uuid().v4()}_${index}'), product: tag.product?.uuid ?? 'Unknown',
items: state.updatedTags, items: state.updatedTags,
initialValue: tag.tag, initialValue: tag,
onSelected: (value) { onSelected: (value) {
controller.text = value; controller.text = value.tag ?? '';
context context.read<AssignTagModelBloc>().add(UpdateTag(
.read<
AssignTagModelBloc>()
.add(UpdateTag(
index: index, index: index,
tag: value, tag: value,
)); ));
@ -217,12 +194,10 @@ class AssignTagModelsDialog extends StatelessWidget {
width: double.infinity, width: double.infinity,
child: DialogDropdown( child: DialogDropdown(
items: locations, items: locations,
selectedValue: tag.location ?? selectedValue: tag.location ?? 'Main Space',
'Main Space',
onSelected: (value) { onSelected: (value) {
context context
.read< .read<AssignTagModelBloc>()
AssignTagModelBloc>()
.add(UpdateLocation( .add(UpdateLocation(
index: index, index: index,
location: value, location: value,
@ -254,17 +229,13 @@ class AssignTagModelsDialog extends StatelessWidget {
builder: (buttonContext) => CancelButton( builder: (buttonContext) => CancelButton(
label: 'Add New Device', label: 'Add New Device',
onPressed: () async { onPressed: () async {
final updatedTags = final updatedTags = List<Tag>.from(state.tags);
List<TagModel>.from(state.tags);
final result = final result =
TagHelper.updateSubspaceTagModels( TagHelper.updateSubspaceTagModels(updatedTags, subspaces);
updatedTags, subspaces);
final processedTags = final processedTags = result['updatedTags'] as List<Tag>;
result['updatedTags'] as List<TagModel>; final processedSubspaces = List<SubspaceTemplateModel>.from(
final processedSubspaces = result['subspaces'] as List<dynamic>);
List<SubspaceTemplateModel>.from(
result['subspaces'] as List<dynamic>);
if (context.mounted) { if (context.mounted) {
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -272,28 +243,25 @@ class AssignTagModelsDialog extends StatelessWidget {
await showDialog<bool>( await showDialog<bool>(
barrierDismissible: false, barrierDismissible: false,
context: context, context: context,
builder: (dialogContext) => builder: (dialogContext) => AddDeviceTypeModelWidget(
AddDeviceTypeModelWidget( products: products,
products: products, subspaces: processedSubspaces,
subspaces: processedSubspaces, isCreate: false,
isCreate: false, initialSelectedProducts:
initialSelectedProducts: TagHelper TagHelper.createInitialSelectedProducts(
.createInitialSelectedProducts( processedTags, processedSubspaces),
processedTags, allTags: allTags,
processedSubspaces), spaceName: spaceName,
allTags: allTags, otherSpaceModels: otherSpaceModels,
spaceName: spaceName, spaceTagModels: processedTags,
otherSpaceModels: otherSpaceModels, pageContext: pageContext,
spaceTagModels: processedTags, projectTags: projectTags,
pageContext: pageContext, spaceModel: SpaceTemplateModel(
spaceModel: SpaceTemplateModel( modelName: spaceName,
modelName: spaceName, tags: updatedTags,
tags: updatedTags, uuid: spaceModel?.uuid,
uuid: spaceModel?.uuid, internalId: spaceModel?.internalId,
internalId: subspaceModels: processedSubspaces)),
spaceModel?.internalId,
subspaceModels:
processedSubspaces)),
); );
} }
}, },
@ -310,22 +278,16 @@ class AssignTagModelsDialog extends StatelessWidget {
: ColorsManager.whiteColorsWithOpacity, : ColorsManager.whiteColorsWithOpacity,
onPressed: state.isSaveEnabled onPressed: state.isSaveEnabled
? () async { ? () async {
final updatedTags = final updatedTags = List<Tag>.from(state.tags);
List<TagModel>.from(state.tags);
final result = final result =
TagHelper.updateSubspaceTagModels( TagHelper.updateSubspaceTagModels(updatedTags, subspaces);
updatedTags, subspaces);
final processedTags = final processedTags = result['updatedTags'] as List<Tag>;
result['updatedTags'] as List<TagModel>; final processedSubspaces = List<SubspaceTemplateModel>.from(
final processedSubspaces = result['subspaces'] as List<dynamic>);
List<SubspaceTemplateModel>.from(
result['subspaces']
as List<dynamic>);
Navigator.of(context) Navigator.of(context).popUntil((route) => route.isFirst);
.popUntil((route) => route.isFirst);
await showDialog( await showDialog(
context: context, context: context,
@ -334,16 +296,15 @@ class AssignTagModelsDialog extends StatelessWidget {
products: products, products: products,
allSpaceModels: allSpaceModels, allSpaceModels: allSpaceModels,
allTags: allTags, allTags: allTags,
projectTags: projectTags,
pageContext: pageContext, pageContext: pageContext,
otherSpaceModels: otherSpaceModels, otherSpaceModels: otherSpaceModels,
spaceModel: SpaceTemplateModel( spaceModel: SpaceTemplateModel(
modelName: spaceName, modelName: spaceName,
tags: processedTags, tags: processedTags,
uuid: spaceModel?.uuid, uuid: spaceModel?.uuid,
internalId: internalId: spaceModel?.internalId,
spaceModel?.internalId, subspaceModels: processedSubspaces),
subspaceModels:
processedSubspaces),
); );
}, },
); );

View File

@ -7,7 +7,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_mo
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/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';
class TagHelper { class TagHelper {
static Map<String, dynamic> updateTags<T>({ static Map<String, dynamic> updateTags<T>({
@ -131,9 +130,9 @@ class TagHelper {
} }
static List<String> getAvailableTagModels( static List<String> getAvailableTagModels(
List<String> allTags, List<TagModel> currentTags, TagModel currentTag) { List<String> allTags, List<Tag> currentTags, Tag currentTag) {
List<String> availableTagsForTagModel = List<String> availableTagsForTagModel =
TagHelper.getAvailableTags<TagModel>( TagHelper.getAvailableTags<Tag>(
allTags: allTags, allTags: allTags,
currentTags: currentTags, currentTags: currentTags,
currentTag: currentTag, currentTag: currentTag,
@ -142,11 +141,11 @@ class TagHelper {
return availableTagsForTagModel; return availableTagsForTagModel;
} }
static List<TagModel> generateInitialTags({ static List<Tag> generateInitialTags({
List<TagModel>? spaceTagModels, List<Tag>? spaceTagModels,
List<SubspaceTemplateModel>? subspaces, List<SubspaceTemplateModel>? subspaces,
}) { }) {
final List<TagModel> initialTags = []; final List<Tag> initialTags = [];
if (spaceTagModels != null) { if (spaceTagModels != null) {
initialTags.addAll(spaceTagModels); initialTags.addAll(spaceTagModels);
@ -212,7 +211,7 @@ class TagHelper {
} }
static List<SelectedProduct> createInitialSelectedProducts( static List<SelectedProduct> createInitialSelectedProducts(
List<TagModel>? tags, List<SubspaceTemplateModel>? subspaces) { List<Tag>? tags, List<SubspaceTemplateModel>? subspaces) {
final Map<ProductModel, int> productCounts = {}; final Map<ProductModel, int> productCounts = {};
if (tags != null) { if (tags != null) {
@ -282,7 +281,7 @@ class TagHelper {
} }
static int? checkTagExistInSubspaceModels( static int? checkTagExistInSubspaceModels(
TagModel tag, List<dynamic>? subspaces) { Tag tag, List<dynamic>? subspaces) {
if (subspaces == null) return null; if (subspaces == null) return null;
for (int i = 0; i < subspaces.length; i++) { for (int i = 0; i < subspaces.length; i++) {
@ -298,8 +297,8 @@ class TagHelper {
} }
static Map<String, dynamic> updateSubspaceTagModels( static Map<String, dynamic> updateSubspaceTagModels(
List<TagModel> updatedTags, List<SubspaceTemplateModel>? subspaces) { List<Tag> updatedTags, List<SubspaceTemplateModel>? subspaces) {
final result = TagHelper.updateTags<TagModel>( final result = TagHelper.updateTags<Tag>(
updatedTags: updatedTags, updatedTags: updatedTags,
subspaces: subspaces, subspaces: subspaces,
getInternalId: (tag) => tag.internalId, getInternalId: (tag) => tag.internalId,
@ -311,7 +310,7 @@ class TagHelper {
checkTagExistInSubspace: checkTagExistInSubspaceModels, checkTagExistInSubspace: checkTagExistInSubspaceModels,
); );
final processedTags = result['updatedTags'] as List<TagModel>; final processedTags = result['updatedTags'] as List<Tag>;
final processedSubspaces = final processedSubspaces =
List<SubspaceTemplateModel>.from(result['subspaces'] as List<dynamic>); List<SubspaceTemplateModel>.from(result['subspaces'] as List<dynamic>);

View File

@ -28,10 +28,11 @@ class LinkSpaceToModelBloc
try { try {
BuildContext context = NavigationService.navigatorKey.currentContext!; BuildContext context = NavigationService.navigatorKey.currentContext!;
var spaceBloc = context.read<SpaceTreeBloc>(); var spaceBloc = context.read<SpaceTreeBloc>();
spacesListIds.clear();
for (var communityId in spaceBloc.state.selectedCommunities) { for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList = List<String> spacesList =
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? []; spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
spacesListIds = spacesList; spacesListIds.addAll(spacesList);
} }
hasSelectedSpaces = hasSelectedSpaces =
spaceBloc.state.selectedCommunities.any((communityId) { spaceBloc.state.selectedCommunities.any((communityId) {

View File

@ -5,6 +5,8 @@ abstract class LinkSpaceToModelState {
class SpaceModelInitial extends LinkSpaceToModelState {} class SpaceModelInitial extends LinkSpaceToModelState {}
class SpaceModelLoading extends LinkSpaceToModelState {} class SpaceModelLoading extends LinkSpaceToModelState {}
class LinkSpaceModelLoading extends LinkSpaceToModelState {}
class SpaceModelSelectedState extends LinkSpaceToModelState { class SpaceModelSelectedState extends LinkSpaceToModelState {
final int selectedIndex; final int selectedIndex;

View File

@ -1,11 +1,11 @@
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.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/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/subspace_template_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/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'; import 'package:syncrow_web/utils/constants/action_enum.dart';
@ -94,14 +94,9 @@ class CreateSpaceModelBloc
orElse: () => subspace, orElse: () => subspace,
); );
// Update the subspace's tags
final eventTagIds = matchingEventSubspace.tags
?.map((e) => e.internalId)
.toSet() ??
{};
final updatedTags = [ final updatedTags = [
...?subspace.tags?.map<TagModel>((tag) { ...?subspace.tags?.map<Tag>((tag) {
final matchingTag = final matchingTag =
matchingEventSubspace.tags?.firstWhere( matchingEventSubspace.tags?.firstWhere(
(e) => e.internalId == tag.internalId, (e) => e.internalId == tag.internalId,
@ -112,14 +107,14 @@ class CreateSpaceModelBloc
? tag.copyWith(tag: matchingTag?.tag) ? tag.copyWith(tag: matchingTag?.tag)
: tag; : tag;
}) ?? }) ??
<TagModel>[], <Tag>[],
...?matchingEventSubspace.tags?.where( ...?matchingEventSubspace.tags?.where(
(e) => (e) =>
subspace.tags subspace.tags
?.every((t) => t.internalId != e.internalId) ?? ?.every((t) => t.internalId != e.internalId) ??
true, true,
) ?? ) ??
<TagModel>[], <Tag>[],
]; ];
return subspace.copyWith( return subspace.copyWith(
subspaceName: matchingEventSubspace.subspaceName, subspaceName: matchingEventSubspace.subspaceName,
@ -244,7 +239,7 @@ class CreateSpaceModelBloc
} }
if (newSubspaces != null) { if (newSubspaces != null) {
for (var newSubspace in newSubspaces!) { for (var newSubspace in newSubspaces) {
// Tag without UUID // Tag without UUID
if ((newSubspace.uuid == null || newSubspace.uuid!.isEmpty)) { if ((newSubspace.uuid == null || newSubspace.uuid!.isEmpty)) {
final List<TagModelUpdate> tagUpdates = []; final List<TagModelUpdate> tagUpdates = [];
@ -253,7 +248,7 @@ class CreateSpaceModelBloc
for (var tag in newSubspace.tags!) { for (var tag in newSubspace.tags!) {
tagUpdates.add(TagModelUpdate( tagUpdates.add(TagModelUpdate(
action: Action.add, action: Action.add,
uuid: tag.uuid == '' ? null : tag.uuid, newTagUuid: tag.uuid == '' ? null : tag.uuid,
tag: tag.tag, tag: tag.tag,
productUuid: tag.product?.uuid)); productUuid: tag.product?.uuid));
} }
@ -268,7 +263,7 @@ class CreateSpaceModelBloc
if (prevSubspaces != null && newSubspaces != null) { if (prevSubspaces != null && newSubspaces != null) {
final newSubspaceMap = { final newSubspaceMap = {
for (var subspace in newSubspaces!) subspace.uuid: subspace for (var subspace in newSubspaces) subspace.uuid: subspace
}; };
for (var prevSubspace in prevSubspaces) { for (var prevSubspace in prevSubspaces) {
@ -309,8 +304,8 @@ class CreateSpaceModelBloc
} }
List<TagModelUpdate> processTagUpdates( List<TagModelUpdate> processTagUpdates(
List<TagModel>? prevTags, List<Tag>? prevTags,
List<TagModel>? newTags, List<Tag>? newTags,
) { ) {
final List<TagModelUpdate> tagUpdates = []; final List<TagModelUpdate> tagUpdates = [];
final processedTags = <String?>{}; final processedTags = <String?>{};
@ -320,7 +315,7 @@ class CreateSpaceModelBloc
tagUpdates.add(TagModelUpdate( tagUpdates.add(TagModelUpdate(
action: Action.add, action: Action.add,
tag: newTag.tag, tag: newTag.tag,
uuid: newTag.uuid, newTagUuid: newTag.uuid,
productUuid: newTag.product?.uuid, productUuid: newTag.product?.uuid,
)); ));
} }
@ -332,7 +327,7 @@ class CreateSpaceModelBloc
if (prevTags != null && newTags != null) { if (prevTags != null && newTags != null) {
for (var prevTag in prevTags) { for (var prevTag in prevTags) {
final existsInNew = final existsInNew =
newTags!.any((newTag) => newTag.uuid == prevTag.uuid); newTags.any((newTag) => newTag.uuid == prevTag.uuid);
if (!existsInNew) { if (!existsInNew) {
tagUpdates tagUpdates
.add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid)); .add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid));
@ -349,14 +344,14 @@ class CreateSpaceModelBloc
if (newTags != null) { if (newTags != null) {
final prevTagUuids = prevTags?.map((t) => t.uuid).toSet() ?? {}; final prevTagUuids = prevTags?.map((t) => t.uuid).toSet() ?? {};
for (var newTag in newTags!) { for (var newTag in newTags) {
// Tag without UUID // Tag without UUID
if ((newTag.uuid == null || !prevTagUuids.contains(newTag.uuid)) && if ((newTag.uuid == null || !prevTagUuids.contains(newTag.uuid)) &&
!processedTags.contains(newTag.tag)) { !processedTags.contains(newTag.tag)) {
tagUpdates.add(TagModelUpdate( tagUpdates.add(TagModelUpdate(
action: Action.add, action: Action.add,
tag: newTag.tag, tag: newTag.tag,
uuid: newTag.uuid == '' ? null : newTag.uuid, newTagUuid: newTag.uuid == '' ? null : newTag.uuid,
productUuid: newTag.product?.uuid)); productUuid: newTag.product?.uuid));
processedTags.add(newTag.tag); processedTags.add(newTag.tag);
} }
@ -365,14 +360,15 @@ class CreateSpaceModelBloc
// Case 3: Tags updated // Case 3: Tags updated
if (prevTags != null && newTags != null) { if (prevTags != null && newTags != null) {
final newTagMap = {for (var tag in newTags!) tag.uuid: tag}; final newTagMap = {for (var tag in newTags) tag.uuid: tag};
for (var prevTag in prevTags!) { for (var prevTag in prevTags) {
final newTag = newTagMap[prevTag.uuid]; final newTag = newTagMap[prevTag.uuid];
if (newTag != null) { if (newTag != null) {
tagUpdates.add(TagModelUpdate( tagUpdates.add(TagModelUpdate(
action: Action.update, action: Action.update,
uuid: newTag.uuid, uuid: prevTag.uuid,
newTagUuid: newTag.uuid,
tag: newTag.tag, tag: newTag.tag,
)); ));
} else {} } else {}

View File

@ -1,7 +1,7 @@
import 'package:equatable/equatable.dart'; 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/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();
@ -49,7 +49,7 @@ class AddSubspacesToSpaceTemplate extends CreateSpaceModelEvent {
} }
class AddTagsToSpaceTemplate extends CreateSpaceModelEvent { class AddTagsToSpaceTemplate extends CreateSpaceModelEvent {
final List<TagModel> tags; final List<Tag> tags;
AddTagsToSpaceTemplate(this.tags); AddTagsToSpaceTemplate(this.tags);
} }

View File

@ -1,7 +1,7 @@
import 'dart:developer';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.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_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';
@ -9,33 +9,30 @@ import 'package:syncrow_web/services/space_model_mang_api.dart';
class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> { class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> {
final SpaceModelManagementApi api; final SpaceModelManagementApi api;
final SpaceTreeBloc _spaceTreeBloc;
SpaceModelBloc({ SpaceModelBloc(
this._spaceTreeBloc, {
required this.api, required this.api,
required List<SpaceTemplateModel> initialSpaceModels, required List<SpaceTemplateModel> initialSpaceModels,
}) : super(SpaceModelLoaded(spaceModels: initialSpaceModels)) { }) : super(SpaceModelLoaded(spaceModels: initialSpaceModels)) {
log('Initial Space Models in: ${initialSpaceModels.map((e) => e.toJson()).toList()}');
on<CreateSpaceModel>(_onCreateSpaceModel); on<CreateSpaceModel>(_onCreateSpaceModel);
on<UpdateSpaceModel>(_onUpdateSpaceModel); on<UpdateSpaceModel>(_onUpdateSpaceModel);
on<DeleteSpaceModel>(_onDeleteSpaceModel); on<DeleteSpaceModel>(_onDeleteSpaceModel);
} }
Future<void> _onCreateSpaceModel( Future<void> _onCreateSpaceModel(CreateSpaceModel event, Emitter<SpaceModelState> emit) async {
CreateSpaceModel event, Emitter<SpaceModelState> emit) async {
final currentState = state; final currentState = state;
if (currentState is SpaceModelLoaded) { if (currentState is SpaceModelLoaded) {
try { try {
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
final newSpaceModel = await api.getSpaceModel( final newSpaceModel = await api.getSpaceModel(event.newSpaceModel.uuid ?? '', projectUuid);
event.newSpaceModel.uuid ?? '', projectUuid);
if (newSpaceModel != null) { if (newSpaceModel != null) {
final updatedSpaceModels = final updatedSpaceModels = List<SpaceTemplateModel>.from(currentState.spaceModels)
List<SpaceTemplateModel>.from(currentState.spaceModels) ..add(newSpaceModel);
..add(newSpaceModel);
emit(SpaceModelLoaded(spaceModels: updatedSpaceModels)); emit(SpaceModelLoaded(spaceModels: updatedSpaceModels));
} }
} catch (e) { } catch (e) {
@ -44,19 +41,18 @@ class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> {
} }
} }
Future<void> _onUpdateSpaceModel( Future<void> _onUpdateSpaceModel(UpdateSpaceModel event, Emitter<SpaceModelState> emit) async {
UpdateSpaceModel event, Emitter<SpaceModelState> emit) async {
final currentState = state; final currentState = state;
if (currentState is SpaceModelLoaded) { if (currentState is SpaceModelLoaded) {
try { try {
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
final newSpaceModel = final newSpaceModel = await api.getSpaceModel(event.spaceModelUuid, projectUuid);
await api.getSpaceModel(event.spaceModelUuid, projectUuid);
if (newSpaceModel != null) { if (newSpaceModel != null) {
final updatedSpaceModels = currentState.spaceModels.map((model) { final updatedSpaceModels = currentState.spaceModels.map((model) {
return model.uuid == event.spaceModelUuid ? newSpaceModel : model; return model.uuid == event.spaceModelUuid ? newSpaceModel : model;
}).toList(); }).toList();
_spaceTreeBloc.add(InitialEvent());
emit(SpaceModelLoaded(spaceModels: updatedSpaceModels)); emit(SpaceModelLoaded(spaceModels: updatedSpaceModels));
} }
} catch (e) { } catch (e) {
@ -65,22 +61,20 @@ class SpaceModelBloc extends Bloc<SpaceModelEvent, SpaceModelState> {
} }
} }
Future<void> _onDeleteSpaceModel( Future<void> _onDeleteSpaceModel(DeleteSpaceModel event, Emitter<SpaceModelState> emit) async {
DeleteSpaceModel event, Emitter<SpaceModelState> emit) async {
final currentState = state; final currentState = state;
if (currentState is SpaceModelLoaded) { if (currentState is SpaceModelLoaded) {
try { try {
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
final deletedSuccessfully = final deletedSuccessfully = await api.deleteSpaceModel(event.spaceModelUuid, projectUuid);
await api.deleteSpaceModel(event.spaceModelUuid, projectUuid);
if (deletedSuccessfully) { if (deletedSuccessfully) {
final updatedSpaceModels = currentState.spaceModels final updatedSpaceModels = currentState.spaceModels
.where((model) => model.uuid != event.spaceModelUuid) .where((model) => model.uuid != event.spaceModelUuid)
.toList(); .toList();
_spaceTreeBloc.add(InitialEvent());
emit(SpaceModelLoaded(spaceModels: updatedSpaceModels)); emit(SpaceModelLoaded(spaceModels: updatedSpaceModels));
} }
} catch (e) { } catch (e) {

View File

@ -6,7 +6,7 @@ class TagBodyModel {
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'uuid': uuid, 'uuid': uuid,
'tag': tag, 'name': tag,
'productUuid': productUuid, 'productUuid': productUuid,
}; };
} }

View File

@ -1,6 +1,6 @@
import 'package:equatable/equatable.dart'; 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/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_update_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';
@ -9,9 +9,9 @@ class SpaceTemplateModel extends Equatable {
String? uuid; String? uuid;
String modelName; String modelName;
List<SubspaceTemplateModel>? subspaceModels; List<SubspaceTemplateModel>? subspaceModels;
final List<TagModel>? tags; final List<Tag>? tags;
String internalId; String internalId;
String? createdAt; DateTime? createdAt;
@override @override
List<Object?> get props => [modelName, subspaceModels, tags]; List<Object?> get props => [modelName, subspaceModels, tags];
@ -24,24 +24,25 @@ class SpaceTemplateModel extends Equatable {
this.tags, this.tags,
this.createdAt, this.createdAt,
}) : internalId = internalId ?? const Uuid().v4(); }) : internalId = internalId ?? const Uuid().v4();
factory SpaceTemplateModel.fromJson(Map<String, dynamic> json) { factory SpaceTemplateModel.fromJson(Map<String, dynamic> json) {
final String internalId = json['internalId'] ?? const Uuid().v4(); final String internalId = json['internalId'] ?? const Uuid().v4();
return SpaceTemplateModel( return SpaceTemplateModel(
uuid: json['uuid'] ?? '', uuid: json['uuid'] ?? '',
createdAt: json['createdAt'] ?? '', createdAt: json['createdAt'] != null
? DateTime.tryParse(json['createdAt'])
: null,
internalId: internalId, internalId: internalId,
modelName: json['modelName'] ?? '', modelName: json['modelName'] ?? '',
subspaceModels: (json['subspaceModels'] as List<dynamic>?) subspaceModels: (json['subspaceModels'] as List<dynamic>?)
?.where((e) => e is Map<String, dynamic>) // Validate type ?.where((e) => e is Map<String, dynamic>)
.map((e) => .map((e) =>
SubspaceTemplateModel.fromJson(e as Map<String, dynamic>)) SubspaceTemplateModel.fromJson(e as Map<String, dynamic>))
.toList() ?? .toList() ??
[], [],
tags: (json['tags'] as List<dynamic>?) tags: (json['tags'] as List<dynamic>?)
?.where((item) => item is Map<String, dynamic>) // Validate type ?.where((item) => item is Map<String, dynamic>) // Validate type
.map((item) => TagModel.fromJson(item as Map<String, dynamic>)) .map((item) => Tag.fromJson(item as Map<String, dynamic>))
.toList() ?? .toList() ??
[], [],
); );
@ -50,7 +51,7 @@ class SpaceTemplateModel extends Equatable {
String? uuid, String? uuid,
String? modelName, String? modelName,
List<SubspaceTemplateModel>? subspaceModels, List<SubspaceTemplateModel>? subspaceModels,
List<TagModel>? tags, List<Tag>? tags,
String? internalId, String? internalId,
}) { }) {
return SpaceTemplateModel( return SpaceTemplateModel(

View File

@ -1,11 +1,11 @@
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:uuid/uuid.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<Tag>? tags;
String internalId; String internalId;
SubspaceTemplateModel({ SubspaceTemplateModel({
@ -25,7 +25,7 @@ class SubspaceTemplateModel {
internalId: internalId, 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) => Tag.fromJson(item))
.toList() ?? .toList() ??
[], [],
); );
@ -44,7 +44,7 @@ class SubspaceTemplateModel {
String? uuid, String? uuid,
String? subspaceName, String? subspaceName,
bool? disabled, bool? disabled,
List<TagModel>? tags, List<Tag>? tags,
String? internalId, String? internalId,
}) { }) {
return SubspaceTemplateModel( return SubspaceTemplateModel(

View File

@ -4,7 +4,7 @@ class CreateTagBodyModel {
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'tag': tag, 'name': tag,
'productUuid': productUuid, 'productUuid': productUuid,
}; };
} }

View File

@ -1,65 +0,0 @@
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
import 'package:uuid/uuid.dart';
class TagModel extends BaseTag {
TagModel({
String? uuid,
required String? tag,
ProductModel? product,
String? internalId,
String? location,
}) : super(
uuid: uuid,
tag: tag,
product: product,
internalId: internalId,
location: location,
);
factory TagModel.fromJson(Map<String, dynamic> json) {
final String internalId = json['internalId'] ?? const Uuid().v4();
return TagModel(
uuid: json['uuid'] ,
internalId: internalId,
tag: json['tag'] ?? '',
product: json['product'] != null
? ProductModel.fromMap(json['product'])
: null,
);
}
@override
TagModel copyWith(
{String? tag,
ProductModel? product,
String? uuid,
String? location,
String? internalId}) {
return TagModel(
tag: tag ?? this.tag,
product: product ?? this.product,
location: location ?? this.location,
internalId: internalId ?? this.internalId,
uuid:uuid?? this.uuid
);
}
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'tag': tag,
'product': product?.toMap(),
};
}
}
extension TagModelExtensions on TagModel {
TagBodyModel toTagBodyModel() {
return TagBodyModel()
..uuid = uuid
..tag = tag ?? ''
..productUuid = product?.uuid;
}
}

View File

@ -5,12 +5,14 @@ class TagModelUpdate {
final String? uuid; final String? uuid;
final String? tag; final String? tag;
final String? productUuid; final String? productUuid;
final String? newTagUuid;
TagModelUpdate({ TagModelUpdate({
required this.action, required this.action,
this.uuid, this.uuid,
this.tag, this.tag,
this.productUuid, this.productUuid,
this.newTagUuid,
}); });
factory TagModelUpdate.fromJson(Map<String, dynamic> json) { factory TagModelUpdate.fromJson(Map<String, dynamic> json) {
@ -26,9 +28,10 @@ class TagModelUpdate {
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'action': action.value, 'action': action.value,
'uuid': uuid, // Nullable field 'tagUuid': uuid,
'tag': tag, 'name': tag,
'productUuid': productUuid, 'productUuid': productUuid,
'newTagUuid': newTagUuid
}; };
} }
} }

View File

@ -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/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/tag.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_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';
@ -12,8 +13,10 @@ import 'package:syncrow_web/utils/color_manager.dart';
class SpaceModelPage extends StatelessWidget { class SpaceModelPage extends StatelessWidget {
final List<ProductModel>? products; final List<ProductModel>? products;
final Function(List<SpaceTemplateModel>)? onSpaceModelsUpdated; final Function(List<SpaceTemplateModel>)? onSpaceModelsUpdated;
final List<Tag> projectTags;
const SpaceModelPage({Key? key, this.products, this.onSpaceModelsUpdated}) const SpaceModelPage(
{Key? key, this.products, this.onSpaceModelsUpdated, required this.projectTags})
: super(key: key); : super(key: key);
@override @override
@ -60,6 +63,7 @@ class SpaceModelPage extends StatelessWidget {
allTags: allTagValues, allTags: allTagValues,
pageContext: context, pageContext: context,
otherSpaceModels: allSpaceModelNames, otherSpaceModels: allSpaceModelNames,
projectTags: projectTags,
); );
}, },
); );
@ -69,8 +73,7 @@ class SpaceModelPage extends StatelessWidget {
} }
// Render existing space model // Render existing space model
final model = spaceModels[index]; final model = spaceModels[index];
final otherModel = final otherModel = List<String>.from(allSpaceModelNames);
List<String>.from(allSpaceModelNames);
otherModel.remove(model.modelName); otherModel.remove(model.modelName);
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
@ -84,6 +87,7 @@ class SpaceModelPage extends StatelessWidget {
otherSpaceModels: otherModel, otherSpaceModels: otherModel,
pageContext: context, pageContext: context,
allSpaceModels: spaceModels, allSpaceModels: spaceModels,
projectTags: projectTags,
); );
}, },
); );
@ -107,10 +111,8 @@ class SpaceModelPage extends StatelessWidget {
return Center( return Center(
child: Text( child: Text(
'Error: ${state.message}', 'Error: ${state.message}',
style: Theme.of(context) style:
.textTheme Theme.of(context).textTheme.bodySmall?.copyWith(color: ColorsManager.warningRed),
.bodySmall
?.copyWith(color: ColorsManager.warningRed),
), ),
); );
} }

View File

@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_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/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.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_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';
@ -25,6 +26,7 @@ class CreateSpaceModelDialog extends StatelessWidget {
final BuildContext? pageContext; final BuildContext? pageContext;
final List<String>? otherSpaceModels; final List<String>? otherSpaceModels;
final List<SpaceTemplateModel>? allSpaceModels; final List<SpaceTemplateModel>? allSpaceModels;
final List<Tag> projectTags;
const CreateSpaceModelDialog( const CreateSpaceModelDialog(
{Key? key, {Key? key,
@ -33,7 +35,8 @@ class CreateSpaceModelDialog extends StatelessWidget {
this.spaceModel, this.spaceModel,
this.pageContext, this.pageContext,
this.otherSpaceModels, this.otherSpaceModels,
this.allSpaceModels}) this.allSpaceModels,
required this.projectTags})
: super(key: key); : super(key: key);
@override @override
@ -68,8 +71,7 @@ class CreateSpaceModelDialog extends StatelessWidget {
spaceNameController.addListener(() { spaceNameController.addListener(() {
bloc.add(UpdateSpaceTemplateName( bloc.add(UpdateSpaceTemplateName(
name: spaceNameController.text, name: spaceNameController.text, allModels: otherSpaceModels ?? []));
allModels: otherSpaceModels ?? []));
}); });
return bloc; return bloc;
@ -87,9 +89,7 @@ class CreateSpaceModelDialog extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
spaceModel?.uuid == null spaceModel?.uuid == null ? 'Create New Space Model' : 'Edit Space Model',
? 'Create New Space Model'
: 'Edit Space Model',
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.headlineLarge .headlineLarge
@ -101,10 +101,8 @@ class CreateSpaceModelDialog extends StatelessWidget {
child: TextField( child: TextField(
controller: spaceNameController, controller: spaceNameController,
onChanged: (value) { onChanged: (value) {
context.read<CreateSpaceModelBloc>().add( context.read<CreateSpaceModelBloc>().add(UpdateSpaceTemplateName(
UpdateSpaceTemplateName( name: value, allModels: otherSpaceModels ?? []));
name: value,
allModels: otherSpaceModels ?? []));
}, },
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
@ -157,6 +155,7 @@ class CreateSpaceModelDialog extends StatelessWidget {
pageContext: pageContext, pageContext: pageContext,
otherSpaceModels: otherSpaceModels, otherSpaceModels: otherSpaceModels,
allSpaceModels: allSpaceModels, allSpaceModels: allSpaceModels,
projectTags: projectTags,
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
SizedBox( SizedBox(
@ -179,84 +178,55 @@ class CreateSpaceModelDialog extends StatelessWidget {
!isNameValid) !isNameValid)
? null ? null
: () { : () {
final updatedSpaceTemplate = final updatedSpaceTemplate = updatedSpaceModel.copyWith(
updatedSpaceModel.copyWith( modelName: spaceNameController.text.trim(),
modelName:
spaceNameController.text.trim(),
); );
if (updatedSpaceModel.uuid == null) { if (updatedSpaceModel.uuid == null) {
context context.read<CreateSpaceModelBloc>().add(
.read<CreateSpaceModelBloc>()
.add(
CreateSpaceTemplate( CreateSpaceTemplate(
spaceTemplate: spaceTemplate: updatedSpaceTemplate,
updatedSpaceTemplate,
onCreate: (newModel) { onCreate: (newModel) {
if (pageContext != null) { if (pageContext != null) {
pageContext!.read<SpaceModelBloc>().add(
CreateSpaceModel(newSpaceModel: newModel));
pageContext! pageContext!
.read<SpaceModelBloc>() .read<SpaceManagementBloc>()
.add(CreateSpaceModel( .add(UpdateSpaceModelCache(newModel));
newSpaceModel:
newModel));
pageContext!
.read<
SpaceManagementBloc>()
.add(
UpdateSpaceModelCache(
newModel));
} }
Navigator.of(context) Navigator.of(context).pop(); // Close the dialog
.pop(); // Close the dialog
}, },
), ),
); );
} else { } else {
if (pageContext != null) { if (pageContext != null) {
final currentState = pageContext! final currentState =
.read<SpaceModelBloc>() pageContext!.read<SpaceModelBloc>().state;
.state; if (currentState is SpaceModelLoaded) {
if (currentState final spaceModels = List<SpaceTemplateModel>.from(
is SpaceModelLoaded) { currentState.spaceModels);
final spaceModels =
List<SpaceTemplateModel>.from(
currentState.spaceModels);
final SpaceTemplateModel? final SpaceTemplateModel? currentSpaceModel =
currentSpaceModel = spaceModels spaceModels.cast<SpaceTemplateModel?>().firstWhere(
.cast<SpaceTemplateModel?>() (sm) => sm?.uuid == updatedSpaceModel.uuid,
.firstWhere(
(sm) =>
sm?.uuid ==
updatedSpaceModel
.uuid,
orElse: () => null, orElse: () => null,
); );
if (currentSpaceModel != null) { if (currentSpaceModel != null) {
context context
.read<CreateSpaceModelBloc>() .read<CreateSpaceModelBloc>()
.add(ModifySpaceTemplate( .add(ModifySpaceTemplate(
spaceTemplate: spaceTemplate: currentSpaceModel,
currentSpaceModel, updatedSpaceTemplate: updatedSpaceTemplate,
updatedSpaceTemplate:
updatedSpaceTemplate,
onUpdate: (newModel) { onUpdate: (newModel) {
if (pageContext != if (pageContext != null) {
null) { pageContext!.read<SpaceModelBloc>().add(
pageContext! UpdateSpaceModel(
.read<
SpaceModelBloc>()
.add(UpdateSpaceModel(
spaceModelUuid: spaceModelUuid:
newModel.uuid ?? newModel.uuid ?? ''));
''));
pageContext! pageContext!
.read< .read<SpaceManagementBloc>()
SpaceManagementBloc>() .add(UpdateSpaceModelCache(newModel));
.add(UpdateSpaceModelCache(
newModel));
} }
Navigator.of(context) Navigator.of(context).pop();
.pop();
})); }));
} }
} }
@ -265,11 +235,11 @@ class CreateSpaceModelDialog extends StatelessWidget {
}, },
backgroundColor: ColorsManager.secondaryColor, backgroundColor: ColorsManager.secondaryColor,
borderRadius: 10, borderRadius: 10,
foregroundColor: ((state.errorMessage != null && foregroundColor:
state.errorMessage != '') || ((state.errorMessage != null && state.errorMessage != '') ||
!isNameValid) !isNameValid)
? ColorsManager.whiteColorsWithOpacity ? ColorsManager.whiteColorsWithOpacity
: ColorsManager.whiteColors, : ColorsManager.whiteColors,
child: const Text('OK'), child: const Text('OK'),
), ),
), ),

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart'; import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_bloc.dart';
import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/link_space_model/bloc/link_space_to_model_event.dart';
@ -45,6 +46,12 @@ class _LinkSpaceModelSpacesDialogState
} }
Widget _buildDialogContent() { Widget _buildDialogContent() {
widget.spaceModel.createdAt.toString();
String formattedDate =
DateFormat('yyyy-MM-dd').format(widget.spaceModel.createdAt!);
String formattedTime =
DateFormat('HH:mm:ss').format(widget.spaceModel.createdAt!);
return Expanded( return Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.all(15.0), padding: const EdgeInsets.all(15.0),
@ -75,8 +82,31 @@ class _LinkSpaceModelSpacesDialogState
const SizedBox(height: 16), const SizedBox(height: 16),
_buildDetailRow( _buildDetailRow(
"Space model name:", widget.spaceModel.modelName), "Space model name:", widget.spaceModel.modelName),
_buildDetailRow("Creation date and time:", Padding(
widget.spaceModel.createdAt.toString()), padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
const Expanded(
child: Text(
"Creation date and time:",
style: const TextStyle(
fontWeight: FontWeight.bold),
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
"$formattedDate $formattedTime",
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black),
),
),
],
),
),
// _buildDetailRow("Creation date and time:",
// widget.spaceModel.createdAt.toString()),
_buildDetailRow("Created by:", "Admin"), _buildDetailRow("Created by:", "Admin"),
const SizedBox(height: 12), const SizedBox(height: 12),
const Text( const Text(

View File

@ -18,8 +18,10 @@ import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_
import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dynamic_room_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';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
import 'package:syncrow_web/utils/string_utils.dart';
class SpaceModelCardWidget extends StatelessWidget { class SpaceModelCardWidget extends StatelessWidget with HelperResponsiveLayout {
final SpaceTemplateModel model; final SpaceTemplateModel model;
final BuildContext? pageContext; final BuildContext? pageContext;
final bool topActionsDisabled; final bool topActionsDisabled;
@ -76,120 +78,109 @@ class SpaceModelCardWidget extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Expanded(
model.modelName, child: Text(
style: Theme.of(context).textTheme.headlineMedium?.copyWith( StringUtils.capitalizeFirstLetter(model.modelName),
color: Colors.black, style: Theme.of(context).textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold, color: ColorsManager.blackColor,
), fontWeight: FontWeight.bold,
maxLines: 1, fontSize: isSmallScreenSize(context) ? 13 : 20,
overflow: TextOverflow.ellipsis, ),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
), ),
Row( if (!topActionsDisabled)
children: [ Row(
InkWell( children: [
onTap: () { InkWell(
showDialog( onTap: () {
context: context, showDialog(
builder: (BuildContext dialogContext) { context: context,
return BlocProvider<LinkSpaceToModelBloc>( builder: (BuildContext dialogContext) {
create: (_) => LinkSpaceToModelBloc(), return BlocProvider<LinkSpaceToModelBloc>(
child: BlocListener<LinkSpaceToModelBloc, create: (_) => LinkSpaceToModelBloc(),
LinkSpaceToModelState>( child: BlocListener<LinkSpaceToModelBloc, LinkSpaceToModelState>(
listener: (context, state) { listenWhen: (previous, current) {
final _bloc = return previous != current;
BlocProvider.of<LinkSpaceToModelBloc>( },
context); listener: (context, state) {
if (state is SpaceModelLoading) { final _bloc = BlocProvider.of<LinkSpaceToModelBloc>(context);
showDialog( if (state is SpaceModelLoading) {
context: context, showDialog(
barrierDismissible: false, context: context,
builder: (BuildContext context) { barrierDismissible: false,
return Dialog( builder: (BuildContext context) {
shape: RoundedRectangleBorder( return Dialog(
borderRadius: shape: RoundedRectangleBorder(
BorderRadius.circular(20)), borderRadius: BorderRadius.circular(20)),
elevation: 10, elevation: 10,
backgroundColor: Colors.white, backgroundColor: Colors.white,
child: Padding( child: Padding(
padding: padding: const EdgeInsets.symmetric(
const EdgeInsets.symmetric( vertical: 30, horizontal: 50),
vertical: 30, child: Column(
horizontal: 50), mainAxisSize: MainAxisSize.min,
child: Column( children: [
mainAxisSize: MainAxisSize.min, CustomLoadingIndicator(),
children: [ ],
CustomLoadingIndicator(), ),
const SizedBox(height: 20),
const Text(
"Linking in progress",
style: TextStyle(
fontSize: 16,
fontWeight:
FontWeight.w500,
color: Colors.black87,
),
),
],
), ),
), );
); },
}, );
); } else if (state is AlreadyHaveLinkedState) {
} else if (state Navigator.of(dialogContext).pop();
is AlreadyHaveLinkedState) { showOverwriteDialog(context, _bloc, model);
Navigator.of(dialogContext).pop(); } else if (state is SpaceValidationSuccess) {
showOverwriteDialog( _bloc.add(LinkSpaceModelEvent(
context, _bloc, model); isOverWrite: false, selectedSpaceMode: model.uuid));
} else if (state
is SpaceValidationSuccess) {
_bloc.add(LinkSpaceModelEvent(
isOverWrite: false,
selectedSpaceMode: model.uuid));
Future.delayed(const Duration(seconds: 1), Future.delayed(const Duration(seconds: 1), () {
() { Navigator.of(dialogContext).pop();
Navigator.of(dialogContext).pop(); Navigator.of(dialogContext).pop();
Navigator.of(dialogContext).pop();
Navigator.of(dialogContext).pop();
});
showDialog(
context: context,
builder: (BuildContext dialogContext) {
return const LinkingSuccessful();
},
).then((v) {
Future.delayed(
const Duration(seconds: 2), () {
Navigator.of(dialogContext).pop(); Navigator.of(dialogContext).pop();
}); });
});
} else if (state is SpaceModelLinkSuccess) { showDialog(
Navigator.of(dialogContext).pop(); context: context,
Navigator.of(dialogContext).pop(); builder: (BuildContext dialogContext) {
showDialog( return const LinkingSuccessful();
context: context, },
builder: (BuildContext dialogContext) { ).then((v) {
return const LinkingSuccessful(); Future.delayed(const Duration(seconds: 2), () {
}, Navigator.of(dialogContext).pop();
); });
} });
}, } else if (state is SpaceModelLinkSuccess) {
child: LinkSpaceModelSpacesDialog( Navigator.of(dialogContext).pop();
spaceModel: model, Navigator.of(dialogContext).pop();
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext successDialogContext) {
Future.delayed(const Duration(seconds: 2), () {
Navigator.of(successDialogContext).pop();
});
return const LinkingSuccessful();
},
);
}
},
child: LinkSpaceModelSpacesDialog(
spaceModel: model,
),
), ),
), );
); },
}, );
); },
}, child: SvgPicture.asset(
child: SvgPicture.asset( Assets.spaceLinkIcon,
Assets.spaceLinkIcon, fit: BoxFit.contain,
fit: BoxFit.contain, ),
), ),
),
if (!topActionsDisabled)
InkWell( InkWell(
onTap: () { onTap: () {
_showDeleteDialog(context); _showDeleteDialog(context);
@ -199,49 +190,8 @@ class SpaceModelCardWidget extends StatelessWidget {
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
), ),
], ],
), ),
// Expanded(
// child: Text(
// model.modelName,
// style:
// Theme.of(context).textTheme.headlineMedium?.copyWith(
// color: Colors.black,
// fontWeight: FontWeight.bold,
// ),
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
// ),
// ),
// if (!topActionsDisabled)
// GestureDetector(
// onTap: () => _showDeleteDialog(context),
// child: Container(
// width: 36, // Adjust size as needed
// height: 36,
// decoration: BoxDecoration(
// shape: BoxShape.circle,
// color: Colors.white,
// boxShadow: [
// BoxShadow(
// color: Colors.black.withOpacity(0.1),
// spreadRadius: 2,
// blurRadius: 5,
// offset: const Offset(0, 2),
// ),
// ],
// ),
// child: Center(
// child: SvgPicture.asset(
// Assets.deleteSpaceModel, // Your actual SVG path
// width: 20,
// height: 20,
// colorFilter: const ColorFilter.mode(
// Colors.grey, BlendMode.srcIn),
// ),
// ),
// ),
// ),
], ],
), ),
if (!showOnlyName) ...[ if (!showOnlyName) ...[
@ -268,8 +218,7 @@ class SpaceModelCardWidget extends StatelessWidget {
), ),
), ),
), ),
if (productTagCount.isNotEmpty && if (productTagCount.isNotEmpty && model.subspaceModels != null)
model.subspaceModels != null)
Container( Container(
width: 1.0, width: 1.0,
color: ColorsManager.softGray, color: ColorsManager.softGray,

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/common/edit_chip.dart'; import 'package:syncrow_web/common/edit_chip.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/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/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart';
@ -10,9 +10,9 @@ import 'package:syncrow_web/utils/color_manager.dart';
class SubspaceModelCreate extends StatefulWidget { class SubspaceModelCreate extends StatefulWidget {
final List<SubspaceTemplateModel> subspaces; final List<SubspaceTemplateModel> subspaces;
final void Function( final void Function(
List<SubspaceTemplateModel> newSubspaces, List<TagModel>? tags)? List<SubspaceTemplateModel> newSubspaces, List<Tag>? tags)?
onSpaceModelUpdate; onSpaceModelUpdate;
final List<TagModel> tags; final List<Tag> tags;
const SubspaceModelCreate({ const SubspaceModelCreate({
Key? key, Key? key,
@ -28,7 +28,7 @@ class SubspaceModelCreate extends StatefulWidget {
class _SubspaceModelCreateState extends State<SubspaceModelCreate> { class _SubspaceModelCreateState extends State<SubspaceModelCreate> {
late List<SubspaceTemplateModel> _subspaces; late List<SubspaceTemplateModel> _subspaces;
String? errorSubspaceId; String? errorSubspaceId;
late List<TagModel> _tags; late List<Tag> _tags;
@override @override
void initState() { void initState() {
@ -117,7 +117,7 @@ class _SubspaceModelCreateState extends State<SubspaceModelCreate> {
.where((s) => !updatedIds.contains(s.internalId)) .where((s) => !updatedIds.contains(s.internalId))
.toList(); .toList();
final List<TagModel> tagsToAppendToSpace = []; final List<Tag> tagsToAppendToSpace = [];
for (var s in deletedSubspaces) { for (var s in deletedSubspaces) {
if (s.tags != null) { if (s.tags != null) {

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/common/edit_chip.dart'; import 'package:syncrow_web/common/edit_chip.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.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/helper/tag_helper.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';
@ -20,6 +21,7 @@ class TagChipDisplay extends StatelessWidget {
final BuildContext? pageContext; final BuildContext? pageContext;
final List<String>? otherSpaceModels; final List<String>? otherSpaceModels;
final List<SpaceTemplateModel>? allSpaceModels; final List<SpaceTemplateModel>? allSpaceModels;
final List<Tag> projectTags;
const TagChipDisplay(BuildContext context, const TagChipDisplay(BuildContext context,
{Key? key, {Key? key,
@ -31,14 +33,14 @@ class TagChipDisplay extends StatelessWidget {
required this.spaceNameController, required this.spaceNameController,
this.pageContext, this.pageContext,
this.otherSpaceModels, this.otherSpaceModels,
this.allSpaceModels}) this.allSpaceModels,
required this.projectTags})
: super(key: key); : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return (spaceModel?.tags?.isNotEmpty == true || return (spaceModel?.tags?.isNotEmpty == true ||
spaceModel?.subspaceModels spaceModel?.subspaceModels?.any((subspace) => subspace.tags?.isNotEmpty == true) ==
?.any((subspace) => subspace.tags?.isNotEmpty == true) ==
true) true)
? SizedBox( ? SizedBox(
width: screenWidth * 0.25, width: screenWidth * 0.25,
@ -59,8 +61,7 @@ class TagChipDisplay extends StatelessWidget {
// Combine tags from spaceModel and subspaces // Combine tags from spaceModel and subspaces
...TagHelper.groupTags([ ...TagHelper.groupTags([
...?spaceModel?.tags, ...?spaceModel?.tags,
...?spaceModel?.subspaceModels ...?spaceModel?.subspaceModels?.expand((subspace) => subspace.tags ?? [])
?.expand((subspace) => subspace.tags ?? [])
]).entries.map( ]).entries.map(
(entry) => Chip( (entry) => Chip(
avatar: SizedBox( avatar: SizedBox(
@ -76,9 +77,7 @@ class TagChipDisplay extends StatelessWidget {
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodySmall! .bodySmall!
.copyWith( .copyWith(color: ColorsManager.spaceColor),
color:
ColorsManager.spaceColor),
), ),
backgroundColor: ColorsManager.whiteColors, backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -105,13 +104,12 @@ class TagChipDisplay extends StatelessWidget {
spaceModel: spaceModel, spaceModel: spaceModel,
otherSpaceModels: otherSpaceModels, otherSpaceModels: otherSpaceModels,
initialTags: TagHelper.generateInitialTags( initialTags: TagHelper.generateInitialTags(
subspaces: subspaces, subspaces: subspaces, spaceTagModels: spaceModel?.tags ?? []),
spaceTagModels: spaceModel?.tags ?? []),
title: 'Edit Device', title: 'Edit Device',
addedProducts: addedProducts: TagHelper.createInitialSelectedProducts(
TagHelper.createInitialSelectedProducts( spaceModel?.tags ?? [], subspaces),
spaceModel?.tags ?? [], subspaces),
spaceName: spaceModel?.modelName ?? '', spaceName: spaceModel?.modelName ?? '',
projectTags: projectTags,
)); ));
}) })
], ],
@ -134,6 +132,7 @@ class TagChipDisplay extends StatelessWidget {
isCreate: true, isCreate: true,
spaceModel: spaceModel, spaceModel: spaceModel,
otherSpaceModels: otherSpaceModels, otherSpaceModels: otherSpaceModels,
projectTags: projectTags,
), ),
); );
}, },

View File

@ -1,6 +1,6 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
abstract class AddDeviceModelState extends Equatable { abstract class AddDeviceModelState extends Equatable {
const AddDeviceModelState(); const AddDeviceModelState();
@ -15,7 +15,7 @@ class AddDeviceModelLoading extends AddDeviceModelState {}
class AddDeviceModelLoaded extends AddDeviceModelState { class AddDeviceModelLoaded extends AddDeviceModelState {
final List<SelectedProduct> selectedProducts; final List<SelectedProduct> selectedProducts;
final List<TagModel> initialTag; final List<Tag> initialTag;
const AddDeviceModelLoaded({ const AddDeviceModelLoaded({
required this.selectedProducts, required this.selectedProducts,

View File

@ -1,7 +1,7 @@
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/all_spaces/model/selected_product_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
abstract class AddDeviceTypeModelEvent extends Equatable { abstract class AddDeviceTypeModelEvent extends Equatable {
const AddDeviceTypeModelEvent(); const AddDeviceTypeModelEvent();
@ -25,7 +25,7 @@ class UpdateProductCountEvent extends AddDeviceTypeModelEvent {
class InitializeDeviceTypeModel extends AddDeviceTypeModelEvent { class InitializeDeviceTypeModel extends AddDeviceTypeModelEvent {
final List<TagModel> initialTags; final List<Tag> initialTags;
final List<SelectedProduct> addedProducts; final List<SelectedProduct> addedProducts;
const InitializeDeviceTypeModel({ const InitializeDeviceTypeModel({

View File

@ -2,13 +2,13 @@ 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/tag.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';
import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart';
import 'package:syncrow_web/pages/spaces_management/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/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/bloc/add_device_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/bloc/add_device_model_state.dart';
@ -20,7 +20,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
final List<ProductModel>? products; final List<ProductModel>? products;
final List<SelectedProduct>? initialSelectedProducts; final List<SelectedProduct>? initialSelectedProducts;
final List<SubspaceTemplateModel>? subspaces; final List<SubspaceTemplateModel>? subspaces;
final List<TagModel>? spaceTagModels; final List<Tag>? spaceTagModels;
final List<String>? allTags; final List<String>? allTags;
final String spaceName; final String spaceName;
final bool isCreate; final bool isCreate;
@ -28,6 +28,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
final BuildContext? pageContext; final BuildContext? pageContext;
final SpaceTemplateModel? spaceModel; final SpaceTemplateModel? spaceModel;
final List<SpaceTemplateModel>? allSpaceModels; final List<SpaceTemplateModel>? allSpaceModels;
final List<Tag> projectTags;
const AddDeviceTypeModelWidget( const AddDeviceTypeModelWidget(
{super.key, {super.key,
@ -41,7 +42,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
this.pageContext, this.pageContext,
this.otherSpaceModels, this.otherSpaceModels,
this.spaceModel, this.spaceModel,
this.allSpaceModels}); this.allSpaceModels,
required this.projectTags});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -78,8 +80,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
Expanded( Expanded(
child: Padding( child: Padding(
padding: padding: const EdgeInsets.symmetric(horizontal: 20.0),
const EdgeInsets.symmetric(horizontal: 20.0),
child: ScrollableGridViewWidget( child: ScrollableGridViewWidget(
isCreate: isCreate, isCreate: isCreate,
products: products, products: products,
@ -112,6 +113,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
allSpaceModels: allSpaceModels, allSpaceModels: allSpaceModels,
products: products, products: products,
allTags: allTags, allTags: allTags,
projectTags: projectTags,
pageContext: pageContext, pageContext: pageContext,
otherSpaceModels: otherSpaceModels, otherSpaceModels: otherSpaceModels,
spaceModel: SpaceTemplateModel( spaceModel: SpaceTemplateModel(
@ -137,6 +139,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
subspaces: subspaces, subspaces: subspaces,
addedProducts: initialSelectedProducts ?? [], addedProducts: initialSelectedProducts ?? [],
allTags: allTags, allTags: allTags,
projectTags: projectTags,
spaceName: spaceName, spaceName: spaceName,
initialTags: initialTags, initialTags: initialTags,
otherSpaceModels: otherSpaceModels, otherSpaceModels: otherSpaceModels,
@ -149,11 +152,10 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
), ),
SizedBox( SizedBox(
width: 140, width: 140,
child: child: BlocBuilder<AddDeviceTypeModelBloc, AddDeviceModelState>(
BlocBuilder<AddDeviceTypeModelBloc, AddDeviceModelState>(
builder: (context, state) { builder: (context, state) {
final isDisabled = state is AddDeviceModelLoaded && final isDisabled =
state.selectedProducts.isEmpty; state is AddDeviceModelLoaded && state.selectedProducts.isEmpty;
return DefaultButton( return DefaultButton(
backgroundColor: ColorsManager.secondaryColor, backgroundColor: ColorsManager.secondaryColor,
@ -166,15 +168,13 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
: () async { : () async {
if (state is AddDeviceModelLoaded && if (state is AddDeviceModelLoaded &&
state.selectedProducts.isNotEmpty) { state.selectedProducts.isNotEmpty) {
final initialTags = final initialTags = TagHelper.generateInitialTags(
TagHelper.generateInitialTags(
spaceTagModels: spaceTagModels, spaceTagModels: spaceTagModels,
subspaces: subspaces, subspaces: subspaces,
); );
final dialogTitle = initialTags.isNotEmpty final dialogTitle =
? 'Edit Device' initialTags.isNotEmpty ? 'Edit Device' : 'Assign Tags';
: 'Assign Tags';
Navigator.of(context).pop(); Navigator.of(context).pop();
await showDialog<bool>( await showDialog<bool>(
context: context, context: context,
@ -184,6 +184,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget {
subspaces: subspaces, subspaces: subspaces,
addedProducts: state.selectedProducts, addedProducts: state.selectedProducts,
allTags: allTags, allTags: allTags,
projectTags: projectTags,
spaceName: spaceName, spaceName: spaceName,
initialTags: initialTags, initialTags: initialTags,
otherSpaceModels: otherSpaceModels, otherSpaceModels: otherSpaceModels,

View File

@ -9,10 +9,10 @@ import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_
import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; import 'package:syncrow_web/pages/visitor_password/model/device_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';
class DevicesManagementApi { class DevicesManagementApi {
Future<List<AllDevicesModel>> fetchDevices(String communityId, String spaceId, String projectId) async { Future<List<AllDevicesModel>> fetchDevices(
String communityId, String spaceId, String projectId) async {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: communityId.isNotEmpty && spaceId.isNotEmpty path: communityId.isNotEmpty && spaceId.isNotEmpty
@ -23,8 +23,9 @@ class DevicesManagementApi {
: ApiEndpoints.getAllDevices.replaceAll('{projectId}', projectId), : ApiEndpoints.getAllDevices.replaceAll('{projectId}', projectId),
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
List<dynamic> jsonData = List<dynamic> jsonData = communityId.isNotEmpty && spaceId.isNotEmpty
communityId.isNotEmpty && spaceId.isNotEmpty ? json['data'] : json; ? json['data']
: json;
List<AllDevicesModel> devicesList = jsonData.map((jsonItem) { List<AllDevicesModel> devicesList = jsonData.map((jsonItem) {
return AllDevicesModel.fromJson(jsonItem); return AllDevicesModel.fromJson(jsonItem);
}).toList(); }).toList();
@ -33,7 +34,7 @@ class DevicesManagementApi {
); );
return response; return response;
} catch (e) { } catch (e) {
debugPrint('Error fetching $e'); debugPrint('fetchDevices Error fetching $e');
return []; return [];
} }
} }
@ -92,7 +93,8 @@ class DevicesManagementApi {
} }
} }
Future<bool> deviceBatchControl(List<String> uuids, String code, dynamic value) async { Future<bool> deviceBatchControl(
List<String> uuids, String code, dynamic value) async {
try { try {
final body = { final body = {
'devicesUuid': uuids, 'devicesUuid': uuids,
@ -116,7 +118,8 @@ class DevicesManagementApi {
} }
} }
static Future<List<DeviceModel>> getDevicesByGatewayId(String gatewayId) async { static Future<List<DeviceModel>> getDevicesByGatewayId(
String gatewayId) async {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.gatewayApi.replaceAll('{gatewayUuid}', gatewayId), path: ApiEndpoints.gatewayApi.replaceAll('{gatewayUuid}', gatewayId),
showServerMessage: false, showServerMessage: false,
@ -150,7 +153,9 @@ class DevicesManagementApi {
String code, String code,
) async { ) async {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getDeviceLogs.replaceAll('{uuid}', uuid).replaceAll('{code}', code), path: ApiEndpoints.getDeviceLogs
.replaceAll('{uuid}', uuid)
.replaceAll('{code}', code),
showServerMessage: false, showServerMessage: false,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return DeviceReport.fromJson(json); return DeviceReport.fromJson(json);
@ -223,7 +228,8 @@ class DevicesManagementApi {
} }
} }
Future<bool> addScheduleRecord(ScheduleEntry sendSchedule, String uuid) async { Future<bool> addScheduleRecord(
ScheduleEntry sendSchedule, String uuid) async {
try { try {
final response = await HTTPService().post( final response = await HTTPService().post(
path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid),
@ -240,7 +246,8 @@ class DevicesManagementApi {
} }
} }
Future<List<ScheduleModel>> getDeviceSchedules(String uuid, String category) async { Future<List<ScheduleModel>> getDeviceSchedules(
String uuid, String category) async {
try { try {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getScheduleByDeviceId path: ApiEndpoints.getScheduleByDeviceId
@ -263,7 +270,9 @@ class DevicesManagementApi {
} }
Future<bool> updateScheduleRecord( Future<bool> updateScheduleRecord(
{required bool enable, required String uuid, required String scheduleId}) async { {required bool enable,
required String uuid,
required String scheduleId}) async {
try { try {
final response = await HTTPService().put( final response = await HTTPService().put(
path: ApiEndpoints.updateScheduleByDeviceId path: ApiEndpoints.updateScheduleByDeviceId
@ -284,7 +293,8 @@ class DevicesManagementApi {
} }
} }
Future<bool> editScheduleRecord(String uuid, ScheduleEntry newSchedule) async { Future<bool> editScheduleRecord(
String uuid, ScheduleEntry newSchedule) async {
try { try {
final response = await HTTPService().put( final response = await HTTPService().put(
path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart';
import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart'; import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart';
import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_scene_model.dart'; import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_scene_model.dart';
import 'package:syncrow_web/pages/routines/models/icon_model.dart'; import 'package:syncrow_web/pages/routines/models/icon_model.dart';
@ -6,7 +7,6 @@ import 'package:syncrow_web/pages/routines/models/routine_details_model.dart';
import 'package:syncrow_web/pages/routines/models/routine_model.dart'; import 'package:syncrow_web/pages/routines/models/routine_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';
class SceneApi { class SceneApi {
static final HTTPService _httpService = HTTPService(); static final HTTPService _httpService = HTTPService();
@ -35,10 +35,11 @@ class SceneApi {
// //
// create automation // create automation
static Future<Map<String, dynamic>> createAutomation( static Future<Map<String, dynamic>> createAutomation(
CreateAutomationModel createAutomationModel) async { CreateAutomationModel createAutomationModel, String projectId) async {
try { try {
final response = await _httpService.post( final response = await _httpService.post(
path: ApiEndpoints.createAutomation, path:
ApiEndpoints.createAutomation.replaceAll('{projectId}', projectId),
body: createAutomationModel.toMap(), body: createAutomationModel.toMap(),
showServerMessage: false, showServerMessage: false,
expectedResponseModel: (json) { expectedResponseModel: (json) {
@ -99,11 +100,14 @@ class SceneApi {
//getAutomation //getAutomation
static Future<List<ScenesModel>> getAutomation(String spaceId) async { static Future<List<ScenesModel>> getAutomation(
String spaceId, String communityId, String projectId) async {
try { try {
final response = await _httpService.get( final response = await _httpService.get(
path: path: ApiEndpoints.getSpaceAutomation
ApiEndpoints.getSpaceAutomation.replaceAll('{spaceUuid}', spaceId), .replaceAll('{spaceUuid}', spaceId)
.replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', projectId),
showServerMessage: false, showServerMessage: false,
expectedResponseModel: (json) { expectedResponseModel: (json) {
List<ScenesModel> scenes = []; List<ScenesModel> scenes = [];
@ -134,11 +138,12 @@ class SceneApi {
//automation details //automation details
static Future<RoutineDetailsModel> getAutomationDetails( static Future<RoutineDetailsModel> getAutomationDetails(
String automationId) async { String automationId, String projectId) async {
try { try {
final response = await _httpService.get( final response = await _httpService.get(
path: ApiEndpoints.getAutomationDetails path: ApiEndpoints.getAutomationDetails
.replaceAll('{automationId}', automationId), .replaceAll('{automationId}', automationId)
.replaceAll('{projectId}', projectId),
showServerMessage: false, showServerMessage: false,
expectedResponseModel: (json) => RoutineDetailsModel.fromMap(json), expectedResponseModel: (json) => RoutineDetailsModel.fromMap(json),
); );
@ -166,12 +171,13 @@ class SceneApi {
} }
//update automation //update automation
static updateAutomation( static updateAutomation(CreateAutomationModel createAutomationModel,
CreateAutomationModel createAutomationModel, String automationId) async { String automationId, String projectId) async {
try { try {
final response = await _httpService.put( final response = await _httpService.put(
path: ApiEndpoints.updateAutomation path: ApiEndpoints.updateAutomation
.replaceAll('{automationId}', automationId), .replaceAll('{automationId}', automationId)
.replaceAll('{projectId}', projectId),
body: createAutomationModel body: createAutomationModel
.toJson(automationId.isNotEmpty == true ? automationId : null), .toJson(automationId.isNotEmpty == true ? automationId : null),
expectedResponseModel: (json) { expectedResponseModel: (json) {
@ -218,12 +224,14 @@ class SceneApi {
// delete automation // delete automation
static Future<bool> deleteAutomation( static Future<bool> deleteAutomation(
{required String unitUuid, required String automationId}) async { {required String unitUuid,
required String automationId,
required String projectId}) async {
try { try {
final response = await _httpService.delete( final response = await _httpService.delete(
path: ApiEndpoints.deleteAutomation path: ApiEndpoints.deleteAutomation
.replaceAll('{automationId}', automationId) .replaceAll('{automationId}', automationId)
.replaceAll('{unitUuid}', unitUuid), .replaceAll('{projectId}', projectId),
showServerMessage: false, showServerMessage: false,
expectedResponseModel: (json) => json['statusCode'] == 200, expectedResponseModel: (json) => json['statusCode'] == 200,
); );
@ -232,4 +240,59 @@ class SceneApi {
rethrow; rethrow;
} }
} }
static Future<bool> updateAutomationStatus(String automationId,
AutomationStatusUpdate createAutomationEnable, String projectId) async {
try {
final response = await _httpService.patch(
path: ApiEndpoints.updateAutomationStatus
.replaceAll('{automationId}', automationId)
.replaceAll('{projectId}', projectId),
body: createAutomationEnable.toMap(),
expectedResponseModel: (json) => json['success'],
);
return response;
} catch (e) {
rethrow;
}
}
static Future<bool> triggerScene(String sceneId) async {
try {
final response = await _httpService.post(
path: ApiEndpoints.triggerScene.replaceAll('{sceneId}', sceneId),
showServerMessage: false,
expectedResponseModel: (json) => json['success'],
);
return response;
} catch (e) {
rethrow;
}
}
static Future<List<ScenesModel>> getAutomationByUnitId(
String unitId,
String communityId,
String projectId,
) async {
try {
final response = await _httpService.get(
path: ApiEndpoints.getUnitAutomation
.replaceAll('{unitUuid}', unitId)
.replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', projectId),
showServerMessage: false,
expectedResponseModel: (json) {
List<ScenesModel> scenes = [];
for (var scene in json) {
scenes.add(ScenesModel.fromJson(scene));
}
return scenes;
},
);
return response;
} catch (e) {
rethrow;
}
}
} }

View File

@ -227,6 +227,7 @@ class CommunitySpaceManagementApi {
required Offset position, required Offset position,
List<TagModelUpdate>? tags, List<TagModelUpdate>? tags,
List<UpdateSubspaceTemplateModel>? subspaces, List<UpdateSubspaceTemplateModel>? subspaces,
String? spaceModelUuid,
required String projectId}) async { required String projectId}) async {
try { try {
final body = { final body = {
@ -238,6 +239,7 @@ class CommunitySpaceManagementApi {
'icon': icon, 'icon': icon,
'subspace': subspaces, 'subspace': subspaces,
'tags': tags, 'tags': tags,
'spaceModelUuid': spaceModelUuid,
}; };
if (parentId != null) { if (parentId != null) {
body['parentUuid'] = parentId; body['parentUuid'] = parentId;
@ -300,4 +302,24 @@ class CommunitySpaceManagementApi {
return []; return [];
} }
} }
Future<List<SpaceModel>> getSpaceOnlyWithDevices(
{String? communityId, String? projectId}) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.spaceOnlyWithDevices
.replaceAll('{communityId}', communityId!)
.replaceAll('{projectId}', projectId!),
expectedResponseModel: (json) {
final spaceModels = (json['data'] as List)
.map((spaceJson) => SpaceModel.fromJson(spaceJson))
.toList();
return spaceModels;
},
);
return response;
} catch (e) {
debugPrint('Error fetching space hierarchy: $e');
return [];
}
}
} }

View File

@ -1,3 +1,4 @@
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart';
import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart';
import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/services/api/http_service.dart';
@ -32,8 +33,8 @@ class SpaceModelManagementApi {
return response; return response;
} }
Future<String?> updateSpaceModel(CreateSpaceTemplateBodyModel spaceModel, Future<String?> updateSpaceModel(
String spaceModelUuid, String projectId) async { CreateSpaceTemplateBodyModel spaceModel, String spaceModelUuid, String projectId) async {
final response = await HTTPService().put( final response = await HTTPService().put(
path: ApiEndpoints.updateSpaceModel path: ApiEndpoints.updateSpaceModel
.replaceAll('{projectId}', projectId) .replaceAll('{projectId}', projectId)
@ -46,8 +47,7 @@ class SpaceModelManagementApi {
return response; return response;
} }
Future<SpaceTemplateModel?> getSpaceModel( Future<SpaceTemplateModel?> getSpaceModel(String spaceModelUuid, String projectId) async {
String spaceModelUuid, String projectId) async {
final response = await HTTPService().get( final response = await HTTPService().get(
path: ApiEndpoints.getSpaceModel path: ApiEndpoints.getSpaceModel
.replaceAll('{projectId}', projectId) .replaceAll('{projectId}', projectId)
@ -102,4 +102,17 @@ class SpaceModelManagementApi {
); );
return response; return response;
} }
Future<List<Tag>> listTags({required String projectId}) async {
final response = await HTTPService().get(
path: ApiEndpoints.listTags.replaceAll('{projectId}', projectId),
expectedResponseModel: (json) {
List<dynamic> jsonData = json['data'];
return jsonData.map((jsonItem) {
return Tag.fromJson(jsonItem);
}).toList();
},
);
return response;
}
} }

View File

@ -52,7 +52,7 @@ abstract class ColorsManager {
static const Color semiTransparentBlackColor = Color(0x3F000000); static const Color semiTransparentBlackColor = Color(0x3F000000);
static const Color transparentColor = Color(0x00000000); static const Color transparentColor = Color(0x00000000);
static const Color spaceColor = Color(0xB2023DFE); static const Color spaceColor = Color(0xB2023DFE);
static const Color counterBackgroundColor = Color.fromARGB(204, 105, 2, 2); static const Color counterBackgroundColor = Color(0xCCF4F4F4);
static const Color neutralGray = Color(0xFFE5E5E5); static const Color neutralGray = Color(0xFFE5E5E5);
static const Color warningRed = Color(0xFFFF6465); static const Color warningRed = Color(0xFFFF6465);
static const Color borderColor = Color(0xFFE5E5E5); static const Color borderColor = Color(0xFFE5E5E5);

View File

@ -86,21 +86,24 @@ abstract class ApiEndpoints {
//product //product
static const String listProducts = '/products'; static const String listProducts = '/products';
static const String getSpaceScenes = '/scene/tap-to-run/{spaceUuid}'; static const String getSpaceScenes = '/scene/tap-to-run/{spaceUuid}';
static const String getSpaceAutomation = '/automation/{spaceUuid}'; static const String getSpaceAutomation =
'/projects/{projectId}/communities/{communityId}/spaces/{spaceUuid}/automations';
static const String getIconScene = '/scene/icon'; static const String getIconScene = '/scene/icon';
static const String createScene = '/scene/tap-to-run'; static const String createScene = '/scene/tap-to-run';
static const String createAutomation = '/automation'; static const String createAutomation = '/projects/{projectId}/automations';
static const String getUnitScenes = static const String getUnitScenes =
'/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/scenes'; '/projects/{projectId}/communities/{communityUuid}/spaces/{spaceUuid}/scenes';
static const String getAutomationDetails = static const String getAutomationDetails =
'/automation/details/{automationId}'; '/projects/{projectId}/automations/{automationId}';
static const String getScene = '/scene/tap-to-run/{sceneId}'; static const String getScene = '/scene/tap-to-run/{sceneId}';
static const String deleteScene = '/scene/tap-to-run/{sceneId}'; static const String deleteScene = '/scene/tap-to-run/{sceneId}';
static const String deleteAutomation = '/automation/{automationId}'; static const String deleteAutomation =
'/projects/{projectId}/automations/{automationId}';
static const String updateScene = '/scene/tap-to-run/{sceneId}'; static const String updateScene = '/scene/tap-to-run/{sceneId}';
static const String updateAutomation = '/automation/{automationId}'; static const String updateAutomation =
'/projects/{projectId}/automations/{automationId}';
//space model //space model
static const String listSpaceModels = '/projects/{projectId}/space-models'; static const String listSpaceModels = '/projects/{projectId}/space-models';
@ -109,6 +112,9 @@ abstract class ApiEndpoints {
'/projects/{projectId}/space-models/{spaceModelUuid}'; '/projects/{projectId}/space-models/{spaceModelUuid}';
static const String updateSpaceModel = static const String updateSpaceModel =
'/projects/{projectId}/space-models/{spaceModelUuid}'; '/projects/{projectId}/space-models/{spaceModelUuid}';
//tag
static const String listTags = '/projects/{projectId}/tags';
static const String linkSpaceModel = static const String linkSpaceModel =
'/projects/{projectId}/space-models/{spaceModelUuid}/spaces/link'; '/projects/{projectId}/space-models/{spaceModelUuid}/spaces/link';
@ -129,4 +135,11 @@ abstract class ApiEndpoints {
static const String terms = '/terms'; static const String terms = '/terms';
static const String policy = '/policy'; static const String policy = '/policy';
static const String userAgreements = '/user/agreements/web/{userUuid}'; static const String userAgreements = '/user/agreements/web/{userUuid}';
static const String triggerScene = '/scene/tap-to-run/{sceneId}/trigger';
static const String updateAutomationStatus =
'/projects/{projectId}/automations/{automationId}';
static const String getUnitAutomation =
'/projects/{projectId}/communities/{communityId}/spaces/{unitUuid}/automations';
static const String spaceOnlyWithDevices =
'/projects/{projectId}/communities/{communityId}/spaces?onlyWithDevices=true';
} }

View File

@ -406,6 +406,9 @@ class Assets {
static const String deleteSpaceLinkIcon = static const String deleteSpaceLinkIcon =
'assets/icons/delete_space_link_icon.svg'; 'assets/icons/delete_space_link_icon.svg';
static const String spaceLinkIcon = 'assets/icons/space_link_icon.svg'; static const String spaceLinkIcon = 'assets/icons/space_link_icon.svg';
static const String successIcon = 'assets/icons/success_icon.svg'; static const String successIcon = 'assets/icons/success_icon.svg';
static const String spaceLocationIcon = 'assets/icons/spaseLocationIcon.svg';
static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.svg';
static const String scenesPlayIconCheck =
'assets/icons/scenesPlayIconCheck.svg';
} }

View File

@ -0,0 +1,6 @@
class StringUtils {
static String capitalizeFirstLetter(String text) {
if (text.isEmpty) return text;
return text[0].toUpperCase() + text.substring(1);
}
}