Merge branch 'dev' of https://github.com/SyncrowIOT/web into feat/space-model

This commit is contained in:
hannathkadher
2025-01-09 13:04:35 +04:00
68 changed files with 6032 additions and 105 deletions

View File

@ -0,0 +1,18 @@
<svg width="40" height="34" viewBox="0 0 40 34" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="2" width="33" height="20" rx="10" fill="#023DFE" fill-opacity="0.7"/>
<g filter="url(#filter0_d_5216_2616)">
<ellipse cx="22.9389" cy="12" rx="8.45122" ry="8.4" fill="white"/>
</g>
<defs>
<filter id="filter0_d_5216_2616" x="6.48767" y="0.599976" width="32.9025" height="32.8" 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 dy="5"/>
<feGaussianBlur stdDeviation="4"/>
<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.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5216_2616"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5216_2616" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1011 B

View File

@ -0,0 +1,3 @@
<svg width="8" height="5" viewBox="0 0 8 5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 0.740463C8 0.574789 7.93931 0.408979 7.8179 0.282701C7.57528 0.0297901 7.18168 0.0297901 6.93903 0.282701L4 3.34404L1.06095 0.282701C0.818322 0.0297898 0.424717 0.0297898 0.182084 0.282701C-0.0606947 0.535291 -0.0606947 0.945601 0.182084 1.19819L3.56055 4.71746C3.80318 4.97034 4.1968 4.97034 4.43945 4.71746L7.8179 1.19819C7.93932 1.07191 8 0.906104 8 0.740463Z" fill="#999999" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 511 B

View File

@ -0,0 +1,3 @@
<svg width="5" height="9" viewBox="0 0 5 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.647445 0.5C0.481771 0.5 0.315961 0.560695 0.189683 0.6821C-0.0632278 0.924716 -0.0632278 1.31832 0.189683 1.56097L3.25102 4.5L0.189683 7.43905C-0.0632278 7.68168 -0.0632278 8.07528 0.189683 8.31792C0.442273 8.56069 0.852583 8.56069 1.10517 8.31792L4.62444 4.93945C4.87732 4.69682 4.87732 4.3032 4.62444 4.06055L1.10517 0.6821C0.978895 0.560678 0.813086 0.5 0.647445 0.5Z" fill="#999999" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 519 B

BIN
assets/icons/atoz_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 B

View File

@ -0,0 +1,9 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 20C15.5228 20 20 15.5228 20 10C20 4.47715 15.5228 0 10 0C4.47715 0 0 4.47715 0 10C0 15.5228 4.47715 20 10 20Z" fill="#BBEC6C"/>
<path d="M9.9999 0C9.79708 0 9.59588 0.00676056 9.39604 0.018672C14.6376 0.330986 18.5355 4.67996 18.5355 10C18.5355 15.32 14.6376 19.669 9.396 19.9813C9.59588 19.9932 9.79708 20 9.9999 20C15.5228 20 19.9999 15.5229 19.9999 10C19.9999 4.47714 15.5228 0 9.9999 0Z" fill="#A2E62E"/>
<path d="M10.0002 18.5355C14.7142 18.5355 18.5357 14.714 18.5357 9.99993C18.5357 5.28586 14.7142 1.46436 10.0002 1.46436C5.28611 1.46436 1.4646 5.28586 1.4646 9.99993C1.4646 14.714 5.28611 18.5355 10.0002 18.5355Z" fill="#4FC123"/>
<path d="M9.99986 1.46436C9.7968 1.46436 9.59551 1.47196 9.396 1.48588C13.8282 1.79574 17.3277 5.48894 17.3277 9.99993C17.3277 14.511 13.8282 18.2041 9.396 18.514C9.59551 18.5279 9.7968 18.5355 9.99986 18.5355C14.7139 18.5355 18.5354 14.714 18.5354 9.99993C18.5354 5.28589 14.7139 1.46436 9.99986 1.46436Z" fill="#47A920"/>
<path d="M15.1631 8.28358L9.71483 13.7323C9.13696 14.3097 8.20095 14.3097 7.62348 13.7323L4.83717 10.946C4.51684 10.626 4.51684 10.1069 4.83717 9.787L5.64924 8.97493V8.97452L5.92449 8.69927C6.24441 8.37935 6.76352 8.37935 7.08344 8.69927L8.47781 10.0936C8.62751 10.2437 8.87097 10.2437 9.02067 10.0936L12.9969 6.11738C13.3168 5.79746 13.836 5.79746 14.1559 6.11738L15.1631 7.12462C15.4834 7.44495 15.4834 7.96366 15.1631 8.28358Z" fill="#D1F19E"/>
<path d="M9.02084 10.0938L8.55927 10.5553C8.37738 10.7376 8.08241 10.7388 7.89851 10.5582L6.48724 9.17265C6.26068 8.94569 5.93432 8.8805 5.64941 8.97507V8.97466L5.92467 8.69941C6.24459 8.37949 6.7637 8.37949 7.08362 8.69941L8.47799 10.0938C8.62768 10.2439 8.87114 10.2439 9.02084 10.0938Z" fill="#BBEC6C"/>
<path d="M15.1633 7.1248L14.1559 6.11747C13.8359 5.79743 13.317 5.79743 12.997 6.11747L12.7366 6.37791C12.971 6.35606 13.213 6.43478 13.3925 6.61429L13.9555 7.12484C14.2755 7.44488 14.2755 7.96375 13.9555 8.28379L8.9588 13.3461C8.60206 13.7028 8.10838 13.8389 7.64697 13.7549C8.22609 14.3094 9.14488 14.3022 9.71466 13.7324L15.1633 8.28379C15.4833 7.96375 15.4833 7.44484 15.1633 7.1248Z" fill="#BBEC6C"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="10" cy="10" r="10" fill="#023DFE"/>
</svg>

After

Width:  |  Height:  |  Size: 151 B

View File

@ -0,0 +1,18 @@
<svg width="39" height="32" viewBox="0 0 39 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="6" y="1" width="33" height="20" rx="10" fill="#E9E9E9"/>
<g filter="url(#filter0_d_5216_2749)">
<circle cx="16" cy="11" r="8" fill="white"/>
</g>
<defs>
<filter id="filter0_d_5216_2749" x="0" y="0" width="32" height="32" 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 dy="5"/>
<feGaussianBlur stdDeviation="4"/>
<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.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5216_2749"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5216_2749" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 956 B

BIN
assets/icons/empty_box.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

View File

@ -0,0 +1,3 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.0001 0.416701C10.0001 0.647137 9.81341 0.833403 9.58339 0.833403H4.16627C3.93625 0.833403 3.74957 0.647137 3.74957 0.416701C3.74957 0.186266 3.93625 0 4.16627 0H9.58339C9.81341 0 10.0001 0.186266 10.0001 0.416701ZM8.33328 2.08351H4.16627C3.93625 2.08351 3.74957 2.26977 3.74957 2.50021C3.74957 2.73064 3.93625 2.91691 4.16627 2.91691H8.33328C8.5633 2.91691 8.74998 2.73064 8.74998 2.50021C8.74998 2.26977 8.5633 2.08351 8.33328 2.08351ZM7.08318 4.16701H4.16627C3.93625 4.16701 3.74957 4.35328 3.74957 4.58372C3.74957 4.81415 3.93625 5.00042 4.16627 5.00042H7.08318C7.3132 5.00042 7.49988 4.81415 7.49988 4.58372C7.49988 4.35328 7.3132 4.16701 7.08318 4.16701ZM5.83307 6.25052H4.16627C3.93625 6.25052 3.74957 6.43679 3.74957 6.66722C3.74957 6.89766 3.93625 7.08392 4.16627 7.08392H5.83307C6.06309 7.08392 6.24977 6.89766 6.24977 6.66722C6.24977 6.43679 6.06309 6.25052 5.83307 6.25052ZM3.21077 8.03942L2.49946 8.75073V0.416701C2.49946 0.186266 2.31278 0 2.08276 0C1.85274 0 1.66606 0.186266 1.66606 0.416701V8.75073L0.954333 8.039C0.791403 7.87607 0.528048 7.87607 0.365118 8.039C0.202187 8.20193 0.202187 8.46529 0.365118 8.62822L1.49313 9.75623C1.65564 9.91874 1.86899 10 2.08276 10C2.29653 10 2.50946 9.91874 2.67198 9.75623L3.79999 8.62822C3.96292 8.46529 3.96292 8.20193 3.79999 8.039C3.63706 7.87607 3.3737 7.87649 3.21077 8.03942Z" fill="#999999"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,7 @@
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.4066 14.8703C18.0603 14.6128 17.571 14.6847 17.3134 15.0309C17.1718 15.2212 17.0202 15.408 16.8629 15.5859C16.5772 15.9092 16.6075 16.4029 16.9308 16.6887C17.0794 16.8201 17.264 16.8846 17.4479 16.8846C17.664 16.8846 17.8792 16.7955 18.0335 16.6208C18.2198 16.4101 18.3993 16.1889 18.5671 15.9634C18.8246 15.6173 18.7528 15.1278 18.4066 14.8703Z" fill="#D5D5D5"/>
<path d="M19.7131 11.2064C19.2917 11.1146 18.8753 11.382 18.7835 11.8036C18.733 12.0355 18.672 12.2678 18.602 12.4942C18.4747 12.9065 18.7056 13.3439 19.1178 13.4713C19.1947 13.495 19.2723 13.5063 19.3487 13.5063C19.6823 13.5063 19.9912 13.2909 20.0949 12.9555C20.1779 12.6868 20.2504 12.411 20.3103 12.1359C20.402 11.7144 20.1347 11.2982 19.7131 11.2064Z" fill="#D5D5D5"/>
<path d="M14.6462 17.3725C14.4387 17.4883 14.224 17.5965 14.0079 17.6941C13.6146 17.8717 13.4398 18.3344 13.6174 18.7277C13.7479 19.0166 14.0322 19.1875 14.3299 19.1875C14.4374 19.1875 14.5466 19.1652 14.651 19.1181C14.9073 19.0023 15.1619 18.874 15.4079 18.7367C15.7846 18.5264 15.9195 18.0505 15.7091 17.6737C15.4988 17.2971 15.0229 17.1622 14.6462 17.3725Z" fill="#D5D5D5"/>
<path d="M9.71886 3.75V9.67641L6.85405 12.5412C6.54897 12.8463 6.54897 13.3409 6.85405 13.646C7.00663 13.7986 7.20651 13.8748 7.40651 13.8748C7.60644 13.8748 7.8064 13.7986 7.95898 13.646L11.0526 10.5524C11.1991 10.4059 11.2814 10.2072 11.2814 10V3.75C11.2814 3.31852 10.9316 2.96875 10.5001 2.96875C10.0686 2.96875 9.71886 3.31852 9.71886 3.75Z" fill="#D5D5D5"/>
<path d="M19.7188 1.67969C19.2873 1.67969 18.9375 2.02945 18.9375 2.46094V4.63031C17.1191 1.77844 13.9434 0 10.5 0C7.82891 0 5.3177 1.0402 3.42891 2.92891C1.5402 4.8177 0.5 7.32891 0.5 10C0.5 12.6711 1.5402 15.1823 3.42891 17.0711C5.3177 18.9598 7.82891 20 10.5 20C10.5066 20 10.513 19.9992 10.5195 19.999C10.5261 19.9992 10.5325 20 10.5391 20C10.8206 20 11.105 19.9881 11.3843 19.9648C11.8142 19.9287 12.1336 19.551 12.0976 19.1211C12.0616 18.6911 11.6843 18.3716 11.2539 18.4077C11.0178 18.4275 10.7773 18.4375 10.5391 18.4375C10.5325 18.4375 10.5261 18.4383 10.5195 18.4385C10.513 18.4383 10.5066 18.4375 10.5 18.4375C5.84754 18.4375 2.0625 14.6525 2.0625 10C2.0625 5.34754 5.84754 1.5625 10.5 1.5625C13.4984 1.5625 16.2561 3.16066 17.7631 5.70312H15.615C15.1835 5.70312 14.8337 6.05289 14.8337 6.48438C14.8337 6.91586 15.1835 7.26562 15.615 7.26562H18C18.4758 7.26562 18.9209 7.13191 19.2999 6.90027C19.3243 6.88637 19.3477 6.87141 19.37 6.85527C20.05 6.40797 20.5 5.63855 20.5 4.76562V2.46094C20.5 2.02945 20.1502 1.67969 19.7188 1.67969Z" fill="#D5D5D5"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 B

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.89852 11.08C-0.632759 8.54864 -0.632708 4.42983 1.89852 1.8985C4.42985 -0.632833 8.54861 -0.632833 11.0799 1.8985C13.2274 4.04599 13.5526 7.23986 12.0565 9.73392C12.0565 9.73392 11.949 9.91422 12.0942 10.0593C12.9222 10.8872 15.4065 13.3716 15.4065 13.3716C16.0658 14.0308 16.2227 14.9527 15.638 15.5374L15.5374 15.638C14.9527 16.2227 14.0308 16.0658 13.3716 15.4065C13.3716 15.4065 10.8926 12.9275 10.0662 12.1012C9.91414 11.9491 9.73389 12.0566 9.73389 12.0566C7.23988 13.5526 4.04601 13.2275 1.89852 11.08ZM9.88131 9.88133C11.7517 8.01094 11.7516 4.96763 9.88125 3.09724C8.01086 1.22689 4.96755 1.22684 3.09721 3.09724C1.22681 4.96758 1.22681 8.01094 3.09721 9.88133C4.9676 11.7516 8.01086 11.7516 9.88131 9.88133Z" fill="black" fill-opacity="0.3"/>
<path d="M9.46694 6.10386C9.554 6.10386 9.6425 6.08674 9.7278 6.05072C10.0686 5.9065 10.228 5.51333 10.0838 5.17253C9.17732 3.03045 6.69723 2.0252 4.5552 2.93164C4.21445 3.07586 4.05503 3.46903 4.19924 3.80983C4.34351 4.15063 4.73658 4.30995 5.07749 4.16579C6.53894 3.54737 8.2312 4.23326 8.84956 5.69471C8.95775 5.95031 9.20588 6.10386 9.46694 6.10386Z" fill="black" fill-opacity="0.3"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="10" cy="10" r="9" stroke="#D5D5D5" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 169 B

View File

@ -0,0 +1,7 @@
<svg width="20" height="18" viewBox="0 0 20 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.5594 12.881L12.3006 12.1281C12.0363 12.04 11.8587 11.7935 11.8587 11.5148V10.3696C12.6718 9.84207 13.248 8.97922 13.39 7.97848C14.2092 7.32457 14.6768 6.35539 14.6768 5.30305C14.6768 3.83754 13.7513 2.5843 12.4541 2.0966C12.2658 0.898984 11.2171 0 10 0C8.78291 0 7.73424 0.899023 7.54592 2.0966C6.24877 2.5843 5.32323 3.83754 5.32323 5.30305C5.32323 6.35535 5.79088 7.32453 6.61002 7.97844C6.75209 8.97918 7.32826 9.84203 8.14139 10.3696V11.5148C8.14139 11.7935 7.96377 12.0399 7.6994 12.128L5.44061 12.881C4.8085 13.0917 4.38385 13.6809 4.38385 14.3471V17.202C4.38385 17.3638 4.51502 17.4949 4.67682 17.4949C4.83862 17.4949 4.96979 17.3638 4.96979 17.202V14.3471C4.96979 13.9335 5.23346 13.5677 5.62588 13.4368L6.35303 13.1945L6.12049 13.6595C5.99178 13.917 5.99178 14.2243 6.12049 14.4817L7.54612 17.333C7.59744 17.4356 7.70088 17.495 7.80838 17.495C7.8524 17.495 7.89709 17.485 7.93916 17.464C8.08389 17.3916 8.14256 17.2157 8.07018 17.071L6.64455 14.2197C6.59791 14.1264 6.59791 14.015 6.64455 13.9216L7.13842 12.9327L7.8599 12.6922L8.77334 17.2594C8.80119 17.3987 8.9235 17.495 9.06033 17.495C9.07936 17.495 9.09869 17.4931 9.11811 17.4893C9.27678 17.4575 9.37967 17.3032 9.34795 17.1445L8.53889 13.0992L9.7994 14.2841C9.85576 14.3371 9.92791 14.3637 10.0001 14.3637C10.0722 14.3637 10.1443 14.3371 10.2007 14.2841L11.4612 13.0992L10.6522 17.1445C10.6205 17.3032 10.7233 17.4575 10.882 17.4893C10.9014 17.4931 10.9207 17.495 10.9398 17.495C11.0766 17.495 11.1989 17.3987 11.2268 17.2594L12.1402 12.6922L12.8615 12.9326L13.3556 13.9216C13.4022 14.0149 13.4022 14.1264 13.3556 14.2197L11.9299 17.0709C11.8576 17.2157 11.9162 17.3916 12.061 17.464C12.103 17.485 12.1477 17.495 12.1917 17.495C12.2992 17.495 12.4027 17.4356 12.454 17.3329L13.8796 14.4817C14.0083 14.2243 14.0083 13.9169 13.8796 13.6595L13.6471 13.1944L14.3742 13.4368C14.7667 13.5676 15.0303 13.9334 15.0303 14.3471V17.2019C15.0303 17.3637 15.1615 17.4949 15.3233 17.4949C15.4851 17.4949 15.6163 17.3637 15.6163 17.2019V14.3471C15.6162 13.6809 15.1915 13.0917 14.5594 12.881ZM10 0.585938C10.8389 0.585938 11.5729 1.14453 11.8145 1.92516C11.6316 1.8948 11.4439 1.87875 11.2526 1.87875H8.74752C8.55616 1.87875 8.36846 1.8948 8.18557 1.92516C8.42717 1.14453 9.16119 0.585938 10 0.585938ZM7.1617 7.49496V6.81496C7.1617 6.32207 7.35365 5.85871 7.70213 5.5102C7.81655 5.39578 7.81655 5.21027 7.70213 5.0959C7.58772 4.98145 7.40221 4.98148 7.28783 5.0959C6.82865 5.55508 6.57576 6.16559 6.57576 6.815V7.13238C6.1469 6.62625 5.9092 5.98582 5.9092 5.30309C5.9092 4.4041 6.32983 3.60199 6.98401 3.08156C7.25975 3.67977 8.31428 5.55266 10.96 6.26395C10.9855 6.27078 11.011 6.27406 11.0362 6.27406C11.1655 6.27406 11.2838 6.18785 11.319 6.05707C11.361 5.90082 11.2684 5.74012 11.1121 5.69809C9.94037 5.38309 8.97006 4.78422 8.22811 3.91816C7.78147 3.39676 7.55647 2.92973 7.48393 2.76262C7.86475 2.57242 8.29369 2.46477 8.74752 2.46477H11.2526C12.8176 2.46477 14.0909 3.73805 14.0909 5.30313C14.0909 5.9859 13.8531 6.62637 13.4242 7.1325C13.4222 6.49598 12.9466 5.9716 12.3163 5.91195C12.1556 5.8966 12.0123 6.01492 11.9971 6.17602C11.9818 6.33711 12.1001 6.48004 12.2611 6.49527C12.5848 6.5259 12.8383 6.80758 12.8383 7.13656V7.49504C12.8383 9.06008 11.5651 10.3334 10 10.3334C8.43498 10.3334 7.1617 9.06 7.1617 7.49496ZM10 13.6686L8.48682 12.2461C8.63932 12.0393 8.72733 11.7852 8.72733 11.5148V10.6738C9.12096 10.832 9.55049 10.9192 10 10.9192C10.4496 10.9192 10.879 10.832 11.2727 10.6738V11.5148C11.2727 11.7852 11.3607 12.0393 11.5132 12.2461L10 13.6686Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M3.4041 12.9931V12.6998C3.60836 12.7636 3.82551 12.798 4.05051 12.798C5.24801 12.798 6.22227 11.8238 6.22227 10.6263V10.426C6.42496 10.1822 6.53539 9.87822 6.53539 9.55721V9.37377C6.53539 8.34893 5.7016 7.51514 4.67676 7.51514H3.42426C2.39941 7.51514 1.56563 8.34889 1.56563 9.37377V9.55721C1.56563 9.87822 1.67605 10.1822 1.87875 10.426V10.6263C1.87875 11.3664 2.25113 12.0209 2.81812 12.4131V12.9931C2.81812 13.1368 2.72652 13.2638 2.59023 13.3092L1.05672 13.8204C0.424688 14.0311 0 14.6203 0 15.2866V17.202C0 17.3638 0.131172 17.495 0.292969 17.495C0.454766 17.495 0.585938 17.3638 0.585938 17.202V15.2866C0.585938 14.8729 0.849609 14.5071 1.24203 14.3763L2.33719 14.0112L3.26172 14.6276C3.31168 14.6609 3.36812 14.6768 3.42395 14.6768C3.51859 14.6768 3.61152 14.631 3.66797 14.5463C3.75773 14.4117 3.72137 14.2298 3.58672 14.1401L3.00359 13.7513C3.24984 13.5831 3.4041 13.3023 3.4041 12.9931ZM2.1516 9.37373C2.1516 8.67197 2.72254 8.10103 3.4243 8.10103H4.6768C5.37855 8.10103 5.94949 8.67197 5.94949 9.37373V9.55717C5.94949 9.70607 5.90723 9.84822 5.82945 9.971L3.20379 9.0958C3.11805 9.06725 3.02387 9.07982 2.94863 9.12998L2.15863 9.65662C2.15445 9.62385 2.15156 9.59076 2.15156 9.55717L2.1516 9.37373ZM2.46473 10.6263V10.1568L3.15445 9.69697L5.63637 10.5243V10.6262C5.63637 11.5006 4.92496 12.2121 4.05055 12.2121C3.17613 12.2121 2.46473 11.5007 2.46473 10.6263Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M1.85864 15.6565C1.69685 15.6565 1.56567 15.7876 1.56567 15.9495V17.202C1.56567 17.3638 1.69685 17.4949 1.85864 17.4949C2.02044 17.4949 2.15161 17.3638 2.15161 17.202V15.9495C2.15161 15.7877 2.02044 15.6565 1.85864 15.6565Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M18.9431 13.8204L17.4096 13.3092C17.2733 13.2638 17.1817 13.1367 17.1817 12.9931V12.4131C17.7487 12.0209 18.1211 11.3664 18.1211 10.6263V10.426C18.3238 10.1822 18.4342 9.87822 18.4342 9.55721V9.37377C18.4342 8.34893 17.6005 7.51514 16.5756 7.51514H15.3231C14.2983 7.51514 13.4645 8.34889 13.4645 9.37377V9.55721C13.4645 9.87822 13.5749 10.1822 13.7776 10.426V10.6263C13.7776 11.8238 14.7519 12.798 15.9494 12.798C16.1744 12.798 16.3915 12.7636 16.5958 12.6998V12.9931C16.5958 13.3023 16.75 13.5831 16.9962 13.7513L16.4131 14.1401C16.2785 14.2298 16.2421 14.4117 16.3319 14.5463C16.3883 14.631 16.4812 14.6768 16.5759 14.6768C16.6317 14.6768 16.6882 14.6609 16.7381 14.6276L17.6626 14.0112L18.7578 14.3763C19.1503 14.5071 19.414 14.8729 19.414 15.2866V17.202C19.414 17.3638 19.5451 17.495 19.7069 17.495C19.8687 17.495 19.9999 17.3638 19.9999 17.202V15.2866C19.9999 14.6203 19.5752 14.0311 18.9431 13.8204ZM14.0504 9.37373C14.0504 8.67197 14.6214 8.10103 15.3231 8.10103H16.5756C17.2774 8.10103 17.8483 8.67197 17.8483 9.37373V9.55717C17.8483 9.70607 17.806 9.84822 17.7283 9.971L15.1026 9.0958C15.0169 9.06725 14.9227 9.07982 14.8475 9.12998L14.0575 9.65662C14.0533 9.62381 14.0504 9.59072 14.0504 9.55717V9.37373ZM14.3635 10.6263V10.1568L15.0533 9.69697L17.5352 10.5243V10.6262C17.5352 11.5006 16.8238 12.2121 15.9494 12.2121C15.0749 12.2121 14.3635 11.5007 14.3635 10.6263Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M18.1414 15.6565C17.9796 15.6565 17.8484 15.7876 17.8484 15.9495V17.202C17.8484 17.3638 17.9796 17.4949 18.1414 17.4949C18.3032 17.4949 18.4343 17.3638 18.4343 17.202V15.9495C18.4343 15.7877 18.3032 15.6565 18.1414 15.6565Z" fill="#023DFE" fill-opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -0,0 +1,10 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 20C15.5228 20 20 15.5228 20 10C20 4.47715 15.5228 0 10 0C4.47715 0 0 4.47715 0 10C0 15.5228 4.47715 20 10 20Z" fill="#FFB09E"/>
<path d="M9.9999 0C9.79708 0 9.59588 0.00676056 9.39604 0.018672C14.6376 0.330986 18.5355 4.67996 18.5355 10C18.5355 15.32 14.6376 19.669 9.396 19.9813C9.59588 19.9932 9.79708 20 9.9999 20C15.5228 20 19.9999 15.5229 19.9999 10C19.9999 4.47714 15.5228 0 9.9999 0Z" fill="#FFA78F"/>
<path d="M10.0002 18.5355C14.7142 18.5355 18.5357 14.714 18.5357 9.99993C18.5357 5.28586 14.7142 1.46436 10.0002 1.46436C5.28611 1.46436 1.4646 5.28586 1.4646 9.99993C1.4646 14.714 5.28611 18.5355 10.0002 18.5355Z" fill="#F07281"/>
<path d="M10.0006 1.46436C9.79753 1.46436 9.59625 1.47196 9.39673 1.48588C13.829 1.79574 17.3284 5.48894 17.3284 9.99993C17.3284 14.511 13.829 18.2041 9.39673 18.514C9.59625 18.5279 9.79753 18.5355 10.0006 18.5355C14.7147 18.5355 18.5362 14.714 18.5362 9.99993C18.5362 5.28589 14.7147 1.46436 10.0006 1.46436Z" fill="#EB5569"/>
<path d="M14.074 12.1165L12.4961 10.5386C12.336 10.3786 12.336 10.119 12.4961 9.95887L14.074 8.38097C14.3867 8.06825 14.3867 7.56121 14.074 7.24849L13.0007 6.17521C12.688 5.86249 12.181 5.86249 11.8682 6.17521L10.2903 7.75312C10.1303 7.9132 9.8707 7.9132 9.71058 7.75312L8.13267 6.17521C7.81996 5.86249 7.31292 5.86249 7.0002 6.17521L5.92692 7.24849C5.6142 7.56121 5.6142 8.06825 5.92692 8.38097L7.50483 9.95887C7.66491 10.119 7.66491 10.3785 7.50483 10.5386L5.92692 12.1165C5.6142 12.4293 5.6142 12.9363 5.92692 13.249L7.0002 14.3223C7.31292 14.635 7.81996 14.635 8.13267 14.3223L9.71058 12.7444C9.87066 12.5843 10.1302 12.5843 10.2903 12.7444L11.8682 14.3223C12.181 14.635 12.688 14.635 13.0007 14.3223L14.074 13.249C14.3867 12.9363 14.3867 12.4293 14.074 12.1165Z" fill="#FFEEE6"/>
<path d="M7.36483 6.63131L9.01368 8.35767C9.17377 8.51775 9.43332 8.51775 9.59344 8.35767L10.0004 7.8732C9.89546 7.8732 9.79055 7.83316 9.71051 7.75312L8.1326 6.17521C7.81988 5.86249 7.31284 5.86249 7.00012 6.17521L6.77759 6.39775C6.98946 6.39223 7.2031 6.46958 7.36483 6.63131Z" fill="#FFDFCF"/>
<path d="M8.96663 12.2028L7.28639 13.9606C7.16578 14.0812 7.0162 14.1547 6.86011 14.1823L7.00011 14.3223C7.31282 14.6351 7.81987 14.6351 8.13258 14.3223L9.71049 12.7444C9.79053 12.6644 9.89544 12.6244 10.0003 12.6244L9.54635 12.2028C9.38627 12.0427 9.12671 12.0427 8.96663 12.2028Z" fill="#FFDFCF"/>
<path d="M12.496 10.5386C12.3359 10.3785 12.3359 10.119 12.496 9.95887L14.0739 8.38097C14.3866 8.06825 14.3866 7.56121 14.0739 7.24849L13.0006 6.17521C12.6879 5.86249 12.1809 5.86249 11.8682 6.17521L11.5521 6.49131C11.8061 6.44008 12.0802 6.51272 12.2772 6.70974L12.8662 7.24845C13.1789 7.56117 13.1789 8.06821 12.8662 8.38093L11.2883 9.95883C11.1282 10.1189 11.1282 10.3785 11.2883 10.5386L12.8662 12.1165C13.1789 12.4292 13.1789 12.9362 12.8662 13.2489L12.3243 13.7721C12.105 13.9914 11.7904 14.0563 11.514 13.968L11.8682 14.3222C12.1809 14.6349 12.6879 14.6349 13.0006 14.3222L14.0739 13.2489C14.3866 12.9362 14.3866 12.4292 14.0739 12.1165L12.496 10.5386Z" fill="#FFDFCF"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
assets/icons/ztoa_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -12,7 +12,7 @@ Future<void> showCustomDialog({
double? iconWidth,
VoidCallback? onOkPressed,
bool barrierDismissible = false,
required List<Widget> actions,
List<Widget>? actions,
}) {
return showDialog(
context: context,

View File

@ -42,7 +42,8 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
Future _fetchUserInfo(FetchUserInfo event, Emitter<HomeState> emit) async {
try {
var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
var uuid =
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
user = await HomeApi().fetchUserInfo(uuid);
emit(HomeInitial());
} catch (e) {

View File

@ -0,0 +1,42 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_state.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/role_model.dart';
class RolesPermissionBloc
extends Bloc<RolesPermissionEvent, RolesPermissionState> {
RolesPermissionBloc() : super(RolesInitial()) {
on<GetRoles>(_getRoles);
on<ChangeTapSelected>(changeTapSelected);
}
List<RoleModel> roleModel = [];
FutureOr<void> _getRoles(
GetRoles event, Emitter<RolesPermissionState> emit) async {
emit(UsersLoadingState());
try {
roleModel = [
RoleModel(roleId: '1', roleImage: '', roleName: 'Admin'),
RoleModel(roleId: '2', roleImage: '', roleName: 'Security'),
RoleModel(roleId: '2', roleImage: '', roleName: 'Reception'),
];
emit(UsersLoadedState());
} catch (e) {
emit(ErrorState(e.toString()));
}
}
bool tapSelect = true;
changeTapSelected(
ChangeTapSelected event, Emitter<RolesPermissionState> emit) {
try {
emit(RolesLoadingState());
tapSelect = event.selected;
emit(ChangeTapStatus(select: !tapSelect));
} catch (e) {
emit(ErrorState(e.toString()));
}
}
}

View File

@ -0,0 +1,41 @@
import 'package:equatable/equatable.dart';
sealed class RolesPermissionEvent extends Equatable {
const RolesPermissionEvent();
}
class GetRoles extends RolesPermissionEvent {
const GetRoles();
@override
List<Object?> get props => [];
}
class GetBatchStatus extends RolesPermissionEvent {
final List<String> uuids;
const GetBatchStatus(this.uuids);
@override
List<Object?> get props => [uuids];
}
class GetDeviceRecords extends RolesPermissionEvent {
final String uuid;
const GetDeviceRecords(this.uuid);
@override
List<Object?> get props => [uuid];
}
class GetDeviceAutomationRecords extends RolesPermissionEvent {
final String uuid;
const GetDeviceAutomationRecords(this.uuid);
@override
List<Object?> get props => [uuid];
}
class ChangeTapSelected extends RolesPermissionEvent {
final bool selected;
const ChangeTapSelected(this.selected);
@override
List<Object?> get props => [selected];
}

View File

@ -0,0 +1,79 @@
import 'package:equatable/equatable.dart';
sealed class RolesPermissionState extends Equatable {
const RolesPermissionState();
}
final class RolesInitial extends RolesPermissionState {
@override
List<Object> get props => [];
}
final class RolesLoadingState extends RolesPermissionState {
@override
List<Object> get props => [];
}
final class UsersLoadingState extends RolesPermissionState {
@override
List<Object> get props => [];
}
final class RolesLoadedState extends RolesPermissionState {
@override
List<Object> get props => [];
}
final class UsersLoadedState extends RolesPermissionState {
@override
List<Object> get props => [];
}
final class ErrorState extends RolesPermissionState {
final String message;
const ErrorState(this.message);
@override
List<Object> get props => [message];
}
/// report state
final class SosReportLoadingState extends RolesPermissionState {
@override
List<Object> get props => [];
}
final class RolesErrorState extends RolesPermissionState {
final String message;
const RolesErrorState(this.message);
@override
List<Object> get props => [message];
}
/// automation reports
final class SosAutomationReportLoadingState extends RolesPermissionState {
@override
List<Object> get props => [];
}
final class SosAutomationReportErrorState extends RolesPermissionState {
final String message;
const SosAutomationReportErrorState(this.message);
@override
List<Object> get props => [message];
}
final class ChangeTapStatus extends RolesPermissionState {
final bool select;
const ChangeTapStatus({required this.select});
@override
List<Object> get props => [select];
}

View File

@ -0,0 +1,267 @@
// import 'dart:convert';
// // Model for Space
// class UserSpaceModel {
// final String uuid;
// final DateTime createdAt;
// final DateTime updatedAt;
// UserSpaceModel({
// required this.uuid,
// required this.createdAt,
// required this.updatedAt,
// });
// factory UserSpaceModel.fromJson(Map<String, dynamic> json) {
// return UserSpaceModel(
// uuid: json['uuid'],
// createdAt: DateTime.parse(json['createdAt']),
// updatedAt: DateTime.parse(json['updatedAt']),
// );
// }
// Map<String, dynamic> toJson() {
// return {
// 'uuid': uuid,
// 'createdAt': createdAt.toIso8601String(),
// 'updatedAt': updatedAt.toIso8601String(),
// };
// }
// }
// // Model for User
// class EditUserModel {
// final String uuid;
// final DateTime createdAt;
// final dynamic email;
// final dynamic? jobTitle;
// final dynamic status;
// final String firstName;
// final String lastName;
// final String? phoneNumber;
// final bool isEnabled;
// final dynamic invitedBy;
// final dynamic roleType;
// final List<UserSpaceModel> spaces;
// final String createdDate;
// final String createdTime;
// EditUserModel({
// required this.uuid,
// required this.createdAt,
// required this.email,
// this.jobTitle,
// required this.status,
// required this.firstName,
// required this.lastName,
// this.phoneNumber,
// required this.isEnabled,
// required this.invitedBy,
// required this.roleType,
// required this.spaces,
// required this.createdDate,
// required this.createdTime,
// });
// factory EditUserModel.fromJson(Map<String, dynamic> json) {
// var spacesList = (json['spaces'] as List)
// .map((spaceJson) => UserSpaceModel.fromJson(spaceJson))
// .toList();
// return EditUserModel(
// uuid: json['uuid'],
// createdAt: DateTime.parse(json['createdAt']),
// email: json['email'],
// jobTitle: json['jobTitle'],
// status: json['status'],
// firstName: json['firstName'],
// lastName: json['lastName'],
// phoneNumber: json['phoneNumber'],
// isEnabled: json['isEnabled'],
// invitedBy: json['invitedBy'],
// roleType: json['roleType'],
// spaces: spacesList,
// createdDate: json['createdDate'],
// createdTime: json['createdTime'],
// );
// }
// Map<String, dynamic> toJson() {
// return {
// 'uuid': uuid,
// 'createdAt': createdAt.toIso8601String(),
// 'email': email,
// 'jobTitle': jobTitle,
// 'status': status,
// 'firstName': firstName,
// 'lastName': lastName,
// 'phoneNumber': phoneNumber,
// 'isEnabled': isEnabled,
// 'invitedBy': invitedBy,
// 'roleType': roleType,
// 'spaces': spaces.map((space) => space.toJson()).toList(),
// 'createdDate': createdDate,
// 'createdTime': createdTime,
// };
// }
// }
class UserProjectResponse {
final int statusCode;
final String message;
final EditUserModel data;
final bool success;
UserProjectResponse({
required this.statusCode,
required this.message,
required this.data,
required this.success,
});
/// Create a [UserProjectResponse] from JSON data
factory UserProjectResponse.fromJson(Map<String, dynamic> json) {
return UserProjectResponse(
statusCode: json['statusCode'] as int,
message: json['message'] as String,
data: EditUserModel.fromJson(json['data'] as Map<String, dynamic>),
success: json['success'] as bool,
);
}
/// Convert the [UserProjectResponse] to JSON
Map<String, dynamic> toJson() {
return {
'statusCode': statusCode,
'message': message,
'data': data.toJson(),
'success': success,
};
}
}
class EditUserModel {
final String uuid;
final String firstName;
final String lastName;
final String email;
final String createdDate; // e.g. "1/3/2025"
final String createdTime; // e.g. "8:41:43 AM"
final String status; // e.g. "invited"
final String invitedBy; // e.g. "SUPER_ADMIN"
final String? phoneNumber; // can be empty
final String? jobTitle; // can be empty
final String roleType; // e.g. "ADMIN"
final List<UserSpaceModel> spaces;
EditUserModel({
required this.uuid,
required this.firstName,
required this.lastName,
required this.email,
required this.createdDate,
required this.createdTime,
required this.status,
required this.invitedBy,
required this.phoneNumber,
required this.jobTitle,
required this.roleType,
required this.spaces,
});
/// Create a [UserData] from JSON data
factory EditUserModel.fromJson(Map<String, dynamic> json) {
return EditUserModel(
uuid: json['uuid'] as String,
firstName: json['firstName'] as String,
lastName: json['lastName'] as String,
email: json['email'] as String,
createdDate: json['createdDate'] as String,
createdTime: json['createdTime'] as String,
status: json['status'] as String,
invitedBy: json['invitedBy'] as String,
phoneNumber: json['phoneNumber'] ?? '',
jobTitle: json['jobTitle'] ?? '',
roleType: json['roleType'] as String,
spaces: (json['spaces'] as List<dynamic>)
.map((e) => UserSpaceModel.fromJson(e as Map<String, dynamic>))
.toList(),
);
}
/// Convert the [UserData] to JSON
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'firstName': firstName,
'lastName': lastName,
'email': email,
'createdDate': createdDate,
'createdTime': createdTime,
'status': status,
'invitedBy': invitedBy,
'phoneNumber': phoneNumber,
'jobTitle': jobTitle,
'roleType': roleType,
'spaces': spaces.map((e) => e.toJson()).toList(),
};
}
}
class UserSpaceModel {
final String uuid;
final String createdAt; // e.g. "2024-11-04T07:20:35.940Z"
final String updatedAt; // e.g. "2024-11-28T18:47:29.736Z"
final dynamic spaceTuyaUuid;
final dynamic spaceName;
final dynamic invitationCode;
final bool disabled;
final double x;
final double y;
final String icon;
UserSpaceModel({
required this.uuid,
required this.createdAt,
required this.updatedAt,
required this.spaceTuyaUuid,
required this.spaceName,
required this.invitationCode,
required this.disabled,
required this.x,
required this.y,
required this.icon,
});
/// Create a [UserSpaceModel] from JSON data
factory UserSpaceModel.fromJson(Map<String, dynamic> json) {
return UserSpaceModel(
uuid: json['uuid'] as String,
createdAt: json['createdAt'] as String,
updatedAt: json['updatedAt'] as String,
spaceTuyaUuid: json['spaceTuyaUuid'] as String?,
spaceName: json['spaceName'] as String,
invitationCode: json['invitationCode'] as String?,
disabled: json['disabled'] as bool,
x: (json['x'] as num).toDouble(),
y: (json['y'] as num).toDouble(),
icon: json['icon'] as String,
);
}
/// Convert the [UserSpaceModel] to JSON
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'createdAt': createdAt,
'updatedAt': updatedAt,
'spaceTuyaUuid': spaceTuyaUuid,
'spaceName': spaceName,
'invitationCode': invitationCode,
'disabled': disabled,
'x': x,
'y': y,
'icon': icon,
};
}
}

View File

@ -0,0 +1,6 @@
class RoleModel {
String? roleId;
String? roleName;
String? roleImage;
RoleModel({this.roleId, this.roleName, this.roleImage});
}

View File

@ -0,0 +1,22 @@
class RoleTypeModel {
final String uuid;
final String createdAt;
final String updatedAt;
final String type;
RoleTypeModel({
required this.uuid,
required this.createdAt,
required this.updatedAt,
required this.type,
});
factory RoleTypeModel.fromJson(Map<String, dynamic> json) {
return RoleTypeModel(
uuid: json['uuid'],
createdAt: json['createdAt'],
updatedAt: json['updatedAt'],
type: json['type'].toString().toLowerCase().replaceAll("_", " "),
);
}
}

View File

@ -0,0 +1,50 @@
class RolesUserModel {
final String uuid;
final DateTime createdAt;
final String email;
final dynamic firstName;
final dynamic lastName;
final dynamic roleType;
final dynamic status;
final bool isEnabled;
final String invitedBy;
final dynamic phoneNumber;
final dynamic jobTitle;
final dynamic createdDate;
final dynamic createdTime;
RolesUserModel({
required this.uuid,
required this.createdAt,
required this.email,
required this.firstName,
required this.lastName,
required this.roleType,
required this.status,
required this.isEnabled,
required this.invitedBy,
this.phoneNumber,
this.jobTitle,
required this.createdDate,
required this.createdTime,
});
factory RolesUserModel.fromJson(Map<String, dynamic> json) {
return RolesUserModel(
uuid: json['uuid'],
createdAt: DateTime.parse(json['createdAt']),
email: json['email'],
firstName: json['firstName'],
lastName: json['lastName'],
roleType: json['roleType'].toString().toLowerCase().replaceAll("_", " "),
status: json['status'],
isEnabled: json['isEnabled'],
invitedBy:
json['invitedBy'].toString().toLowerCase().replaceAll("_", " "),
phoneNumber: json['phoneNumber'],
jobTitle: json['jobTitle'].toString(),
createdDate: json['createdDate'],
createdTime: json['createdTime'],
);
}
}

View File

@ -0,0 +1,478 @@
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/custom_dialog.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/edit_user_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
import 'package:syncrow_web/services/space_mana_api.dart';
import 'package:syncrow_web/services/user_permission.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class UsersBloc extends Bloc<UsersEvent, UsersState> {
UsersBloc() : super(UsersInitial()) {
on<CheckStepStatus>(isCompleteBasicsFun);
on<LoadCommunityAndSpacesEvent>(_onLoadCommunityAndSpaces);
on<SearchAnode>(searchTreeNode);
on<CheckSpacesStepStatus>(isCompleteSpacesFun);
on<RoleEvent>(_getRolePermission);
on<PermissionEvent>(_getPermissions);
on<SearchPermission>(searchRolePermission);
on<SendInviteUsers>(_sendInvitUser);
on<ValidateBasicsStep>(_validateBasicsStep);
on<CheckRoleStepStatus>(isCompleteRoleFun);
on<CheckEmailEvent>(checkEmail);
on<GetUserByIdEvent>(getUserById);
on<ToggleNodeExpansion>(_onToggleNodeExpansion);
on<ToggleNodeCheck>(_onToggleNodeCheck);
on<EditInviteUsers>(_editInviteUser);
}
void _validateBasicsStep(ValidateBasicsStep event, Emitter<UsersState> emit) {
if (formKey.currentState?.validate() ?? false) {
emit(const BasicsStepValidState());
} else {
emit(const BasicsStepInvalidState());
}
}
String roleSelected = '';
final formKey = GlobalKey<FormState>();
final TextEditingController firstNameController = TextEditingController();
final TextEditingController lastNameController = TextEditingController();
final TextEditingController emailController = TextEditingController();
final TextEditingController phoneController = TextEditingController();
final TextEditingController jobTitleController = TextEditingController();
final TextEditingController roleSearchController = TextEditingController();
bool? isCompleteBasics;
bool? isCompleteRolePermissions;
bool? isCompleteSpaces;
int numberBasics = 0;
int numberSpaces = 0;
int numberRole = 0;
void isCompleteSpacesFun(
CheckSpacesStepStatus event, Emitter<UsersState> emit) {
emit(UsersLoadingState());
List<String> selectedIds = getSelectedIds(updatedCommunities);
isCompleteSpaces = selectedIds.isNotEmpty;
emit(ChangeStatusSteps());
}
void isCompleteRoleFun(CheckRoleStepStatus event, Emitter<UsersState> emit) {
emit(UsersLoadingState());
isCompleteRolePermissions = roleSelected != '';
emit(ChangeStatusSteps());
}
Future<List<SpaceModel>> _fetchSpacesForCommunity(
String communityUuid) async {
return await CommunitySpaceManagementApi().getSpaceHierarchy(communityUuid);
}
List<TreeNode> updatedCommunities = [];
List<TreeNode> spacesNodes = [];
_onLoadCommunityAndSpaces(
LoadCommunityAndSpacesEvent event, Emitter<UsersState> emit) async {
try {
emit(UsersLoadingState());
List<CommunityModel> communities =
await CommunitySpaceManagementApi().fetchCommunities();
updatedCommunities = await Future.wait(
communities.map((community) async {
List<SpaceModel> spaces =
await _fetchSpacesForCommunity(community.uuid);
spacesNodes = _buildTreeNodes(spaces);
return TreeNode(
uuid: community.uuid,
title: community.name,
children: spacesNodes,
isChecked: false,
isHighlighted: false,
isExpanded: true,
);
}).toList(),
);
emit(const SpacesLoadedState());
return updatedCommunities;
} catch (e) {
emit(ErrorState('Error loading communities and spaces: $e'));
}
}
List<TreeNode> _buildTreeNodes(List<SpaceModel> spaces) {
return spaces.map((space) {
List<TreeNode> childNodes =
space.children.isNotEmpty ? _buildTreeNodes(space.children) : [];
return TreeNode(
uuid: space.uuid!,
title: space.name,
isChecked: false,
isHighlighted: false,
isExpanded: childNodes.isNotEmpty,
children: childNodes,
);
}).toList();
}
void searchTreeNode(SearchAnode event, Emitter<UsersState> emit) {
emit(UsersLoadingState());
if (event.searchTerm!.isEmpty) {
_clearHighlights(updatedCommunities);
} else {
_searchAndHighlightNodes(updatedCommunities, event.searchTerm!);
}
emit(ChangeStatusSteps());
}
void _clearHighlights(List<TreeNode> nodes) {
for (var node in nodes) {
node.isHighlighted = false;
if (node.children.isNotEmpty) {
_clearHighlights(node.children);
}
}
}
bool _searchAndHighlightNodes(List<TreeNode> nodes, String searchTerm) {
bool anyMatch = false;
for (var node in nodes) {
bool isMatch =
node.title.toLowerCase().contains(searchTerm.toLowerCase());
bool childMatch = _searchAndHighlightNodes(node.children, searchTerm);
node.isHighlighted = isMatch || childMatch;
anyMatch = anyMatch || node.isHighlighted;
}
return anyMatch;
}
List<String> selectedIds = [];
List<String> getSelectedIds(List<TreeNode> nodes) {
List<String> selectedIds = [];
for (var node in nodes) {
if (node.isChecked) {
selectedIds.add(node.uuid);
}
if (node.children.isNotEmpty) {
selectedIds.addAll(getSelectedIds(node.children));
}
}
return selectedIds;
}
List<RoleTypeModel> roles = [];
List<PermissionOption> permissions = [];
_getRolePermission(RoleEvent event, Emitter<UsersState> emit) async {
try {
emit(UsersLoadingState());
roles = await UserPermissionApi().fetchRoles();
// add(PermissionEvent(roleUuid: roles.first.uuid));
emit(RolePermissionInitial());
} catch (e) {
emit(ErrorState('Error loading communities and spaces: $e'));
}
}
_getPermissions(PermissionEvent event, Emitter<UsersState> emit) async {
try {
emit(UsersLoadingState());
permissions = await UserPermissionApi().fetchPermission(
event.roleUuid == "" ? roles.first.uuid : event.roleUuid);
roleSelected = event.roleUuid!;
emit(RolePermissionInitial());
} catch (e) {
emit(ErrorState('Error loading communities and spaces: $e'));
}
}
bool _searchRolePermission(List<PermissionOption> nodes, String searchTerm) {
bool anyMatch = false;
for (var node in nodes) {
bool isMatch =
node.title.toLowerCase().contains(searchTerm.toLowerCase());
bool childMatch = _searchRolePermission(node.subOptions, searchTerm);
node.isHighlighted = isMatch || childMatch;
anyMatch = anyMatch || node.isHighlighted;
}
return anyMatch;
}
_sendInvitUser(SendInviteUsers event, Emitter<UsersState> emit) async {
try {
emit(UsersLoadingState());
List<String> selectedIds = getSelectedIds(updatedCommunities);
bool res = await UserPermissionApi().sendInviteUser(
email: emailController.text,
firstName: firstNameController.text,
jobTitle: jobTitleController.text,
lastName: lastNameController.text,
phoneNumber: phoneController.text,
roleUuid: roleSelected,
spaceUuids: selectedIds,
);
if (res == true) {
showCustomDialog(
barrierDismissible: false,
context: event.context,
message: "The invite was sent successfully.",
iconPath: Assets.deviceNoteIcon,
title: "Invite Success",
dialogHeight: MediaQuery.of(event.context).size.height * 0.3,
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(event.context).pop(true);
Navigator.of(event.context).pop(true);
},
child: const Text('OK'),
),
],
);
} else {
emit(const ErrorState('Failed to send invite.'));
}
emit(SaveState());
} catch (e) {
emit(ErrorState('Failed to send invite: ${e.toString()}'));
}
}
_editInviteUser(EditInviteUsers event, Emitter<UsersState> emit) async {
try {
emit(UsersLoadingState());
List<String> selectedIds = getSelectedIds(updatedCommunities);
bool res = await UserPermissionApi().editInviteUser(
userId: event.userId,
firstName: firstNameController.text,
jobTitle: jobTitleController.text,
lastName: lastNameController.text,
phoneNumber: phoneController.text,
roleUuid: roleSelected,
spaceUuids: selectedIds,
);
if (res == true) {
showCustomDialog(
barrierDismissible: false,
context: event.context,
message: "The invite was sent successfully.",
iconPath: Assets.deviceNoteIcon,
title: "Invite Success",
dialogHeight: MediaQuery.of(event.context).size.height * 0.3,
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(event.context).pop(true);
Navigator.of(event.context).pop(true);
},
child: const Text('OK'),
),
],
).then(
(value) {},
);
} else {
emit(const ErrorState('Failed to send invite.'));
}
emit(SaveState());
} catch (e) {
emit(ErrorState('Failed to send invite: ${e.toString()}'));
}
}
void searchRolePermission(SearchPermission event, Emitter<UsersState> emit) {
emit(UsersLoadingState());
if (event.searchTerm!.isEmpty) {
_clearHighlightsRolePermission(permissions);
} else {
_searchRolePermission(permissions, event.searchTerm!);
}
emit(ChangeStatusSteps());
}
String checkEmailValid = '';
Future<void> checkEmail(
CheckEmailEvent event, Emitter<UsersState> emit) async {
emit(UsersLoadingState());
String? res = await UserPermissionApi().checkEmail(
emailController.text,
);
checkEmailValid = res!;
emit(ChangeStatusSteps());
}
bool isCompleteBasicsFun(CheckStepStatus event, Emitter<UsersState> emit) {
emit(UsersLoadingState());
if (event.isEditUser == false) {
add(const CheckEmailEvent());
final emailRegex = RegExp(
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
);
bool isEmailValid = emailRegex.hasMatch(emailController.text);
bool isEmailServerValid = checkEmailValid == 'Valid email';
isCompleteBasics = firstNameController.text.isNotEmpty &&
lastNameController.text.isNotEmpty &&
emailController.text.isNotEmpty &&
isEmailValid &&
isEmailServerValid;
} else {
isCompleteBasics = firstNameController.text.isNotEmpty &&
lastNameController.text.isNotEmpty;
}
emit(ChangeStatusSteps());
emit(ValidateBasics());
return isCompleteBasics!;
}
void _clearHighlightsRolePermission(List<PermissionOption> nodes) {
for (var node in nodes) {
node.isHighlighted = false;
if (node.subOptions.isNotEmpty) {
_clearHighlightsRolePermission(node.subOptions);
}
}
}
EditUserModel? res = EditUserModel(
spaces: [],
jobTitle: '',
phoneNumber: '',
uuid: '',
email: '',
firstName: '',
lastName: '',
roleType: '',
status: '',
invitedBy: '',
createdDate: '',
createdTime: '');
Future<void> getUserById(
GetUserByIdEvent event,
Emitter<UsersState> emit,
) async {
emit(UsersLoadingState());
try {
if (event.uuid?.isNotEmpty ?? false) {
final res = await UserPermissionApi().fetchUserById(event.uuid);
if (res != null) {
// Populate the text controllers
firstNameController.text = res.firstName;
lastNameController.text = res.lastName;
emailController.text = res.email;
phoneController.text = res.phoneNumber ?? '';
jobTitleController.text = res.jobTitle ?? '';
res.roleType;
if (updatedCommunities.isNotEmpty) {
// Create a list of UUIDs to mark
final uuidsToMark = res.spaces.map((space) => space.uuid).toList();
// Print all IDs and mark nodes in updatedCommunities
debugPrint('Printing and marking nodes in updatedCommunities:');
_printAndMarkNodes(updatedCommunities, uuidsToMark);
}
final roleId = roles
.firstWhere((element) =>
element.type ==
res.roleType.toString().toLowerCase().replaceAll("_", " "))
.uuid;
debugPrint('Role ID: $roleId');
roleSelected = roleId;
add(PermissionEvent(roleUuid: roleSelected));
emit(ChangeStatusSteps());
} else {}
} else {}
} catch (_) {}
}
void _printAndMarkNodes(List<TreeNode> nodes, List<String> uuidsToMark,
[int level = 0]) {
for (final node in nodes) {
if (uuidsToMark.contains(node.uuid)) {
node.isChecked = true;
debugPrint(
'${' ' * level}MATCH FOUND: Node ID: ${node.uuid}, Title: ${node.title} is marked as checked.');
} else {
debugPrint(
'${' ' * level}Node ID: ${node.uuid}, Title: ${node.title}');
}
if (node.children.isNotEmpty) {
_printAndMarkNodes(node.children, uuidsToMark, level + 1);
}
}
}
void _onToggleNodeExpansion(
ToggleNodeExpansion event,
Emitter<UsersState> emit,
) {
emit(UsersLoadingState());
event.node.isExpanded = !event.node.isExpanded;
emit(ChangeStatusSteps());
}
void _onToggleNodeCheck(
ToggleNodeCheck event,
Emitter<UsersState> emit,
) {
emit(UsersLoadingState());
//Toggle node's checked state
event.node.isChecked = !event.node.isChecked;
debugPrint(
'Node toggled. ID: ${event.node.uuid}, isChecked: ${event.node.isChecked}',
);
// Update children and parent
_updateChildrenCheckStatus(event.node, event.node.isChecked);
_updateParentCheckStatus(event.node);
// Finally, emit a new state
emit(ChangeStatusSteps());
}
void _updateChildrenCheckStatus(TreeNode node, bool isChecked) {
for (var child in node.children) {
child.isChecked = isChecked;
_updateChildrenCheckStatus(child, isChecked);
}
}
void _updateParentCheckStatus(TreeNode node) {
TreeNode? parent = _findParent(updatedCommunities, node);
if (parent != null) {
parent.isChecked = _areAllChildrenChecked(parent);
_updateParentCheckStatus(parent);
}
}
bool _areAllChildrenChecked(TreeNode node) {
return node.children.isNotEmpty &&
node.children.every((child) =>
child.isChecked &&
(child.children.isEmpty || _areAllChildrenChecked(child)));
}
TreeNode? _findParent(List<TreeNode> nodes, TreeNode target) {
for (var node in nodes) {
if (node.children.contains(target)) {
return node;
}
final parent = _findParent(node.children, target);
if (parent != null) {
return parent;
}
}
return null;
}
}

View File

@ -0,0 +1,176 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart';
sealed class UsersEvent extends Equatable {
const UsersEvent();
}
class SendInviteUsers extends UsersEvent {
final BuildContext context;
const SendInviteUsers({required this.context});
@override
List<Object?> get props => [context];
}
class EditInviteUsers extends UsersEvent {
final BuildContext context;
final String userId;
const EditInviteUsers({required this.context, required this.userId});
@override
List<Object?> get props => [context, userId];
}
class CheckSpacesStepStatus extends UsersEvent {
const CheckSpacesStepStatus();
@override
List<Object?> get props => [];
}
class CheckRoleStepStatus extends UsersEvent {
const CheckRoleStepStatus();
@override
List<Object?> get props => [];
}
class LoadCommunityAndSpacesEvent extends UsersEvent {
const LoadCommunityAndSpacesEvent();
@override
List<Object?> get props => [];
}
class RoleEvent extends UsersEvent {
const RoleEvent();
@override
List<Object?> get props => [];
}
class PermissionEvent extends UsersEvent {
final String? roleUuid;
const PermissionEvent({this.roleUuid = ""});
@override
List<Object?> get props => [roleUuid];
}
class GetBatchStatus extends UsersEvent {
final List<String> uuids;
const GetBatchStatus(this.uuids);
@override
List<Object?> get props => [uuids];
}
//isEditUser:widget.userId!=''? false:true
class CheckStepStatus extends UsersEvent {
final int? steps;
final bool? isEditUser;
const CheckStepStatus({this.steps, required this.isEditUser});
@override
List<Object?> get props => [steps];
}
class SearchAnode extends UsersEvent {
final List<TreeNode>? nodes;
final String? searchTerm;
const SearchAnode({this.nodes, this.searchTerm});
@override
List<Object?> get props => [nodes, searchTerm];
}
class SearchPermission extends UsersEvent {
final List<PermissionOption>? nodes;
final String? searchTerm;
const SearchPermission({this.nodes, this.searchTerm});
@override
List<Object?> get props => [nodes, searchTerm];
}
class SelectedId extends UsersEvent {
final List<TreeNode>? nodes;
const SelectedId({
this.nodes,
});
@override
List<Object?> get props => [nodes];
}
class ValidateBasicsStep extends UsersEvent {
const ValidateBasicsStep();
@override
List<Object?> get props => [];
}
class CheckEmailEvent extends UsersEvent {
const CheckEmailEvent();
@override
List<Object?> get props => [];
}
class GetUserByIdEvent extends UsersEvent {
final String? uuid;
const GetUserByIdEvent({this.uuid});
@override
List<Object?> get props => [uuid];
}
class ToggleNodeExpansion extends UsersEvent {
final TreeNode node;
const ToggleNodeExpansion({required this.node});
@override
List<Object?> get props => [node];
}
class UpdateNodeCheckStatus extends UsersEvent {
final TreeNode node;
const UpdateNodeCheckStatus({required this.node});
@override
List<Object?> get props => [node];
}
class ToggleNodeHighlightEvent extends UsersEvent {
final TreeNode node;
const ToggleNodeHighlightEvent(this.node);
@override
List<Object?> get props => [node];
}
class ExpandAllNodesEvent extends UsersEvent {
@override
List<Object?> get props => [];
}
class CollapseAllNodesEvent extends UsersEvent {
@override
List<Object?> get props => [];
}
class ClearSelectionsEvent extends UsersEvent {
@override
List<Object?> get props => [];
}
class ToggleNodeCheckEvent extends UsersEvent {
final TreeNode node;
const ToggleNodeCheckEvent(this.node);
@override
List<Object?> get props => [];
}
class ToggleNodeCheck extends UsersEvent {
final TreeNode node;
const ToggleNodeCheck(this.node);
@override
List<Object?> get props => [];
}
class EditUserEvent extends UsersEvent {
const EditUserEvent();
@override
List<Object?> get props => [];
}

View File

@ -0,0 +1,91 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart';
sealed class UsersState extends Equatable {
const UsersState();
}
final class UsersInitial extends UsersState {
@override
List<Object> get props => [];
}
final class RolePermissionInitial extends UsersState {
@override
List<Object> get props => [];
}
final class ChangeStatusSteps extends UsersState {
@override
List<Object> get props => [];
}
final class UsersLoadingState extends UsersState {
@override
List<Object> get props => [];
}
final class SaveState extends UsersState {
@override
List<Object> get props => [];
}
final class SpacesLoadedState extends UsersState {
const SpacesLoadedState();
@override
List<Object> get props => [];
}
final class ErrorState extends UsersState {
final String message;
const ErrorState(this.message);
@override
List<Object> get props => [message];
}
final class RolesErrorState extends UsersState {
final String message;
const RolesErrorState(this.message);
@override
List<Object> get props => [message];
}
/// automation reports
final class ChangeTapStatus extends UsersState {
final bool select;
const ChangeTapStatus({required this.select});
@override
List<Object> get props => [select];
}
final class BasicsStepValidState extends UsersState {
const BasicsStepValidState();
@override
List<Object> get props => [];
}
class BasicsStepInvalidState extends UsersState {
const BasicsStepInvalidState();
@override
List<Object> get props => [];
}
final class ValidateBasics extends UsersState {
@override
List<Object> get props => [];
}
class UsersLoadedState extends UsersState {
final List<TreeNode> updatedCommunities;
const UsersLoadedState({required this.updatedCommunities});
@override
List<Object> get props => [];
}

View File

@ -0,0 +1,42 @@
class PermissionOption {
String id;
String title;
bool isChecked;
bool isHighlighted;
List<PermissionOption> subOptions;
PermissionOption({
required this.id,
required this.title,
this.isChecked = false,
this.isHighlighted = false,
this.subOptions = const [],
});
factory PermissionOption.fromJson(Map<String, dynamic> json) {
return PermissionOption(
id: json['id'] ?? '',
title: json['title'] != null
? json['title'].toString().toLowerCase().replaceAll("_", " ")
: '',
isChecked: json['isChecked'] ?? false,
isHighlighted: json['isHighlighted'] ?? false,
subOptions: (json['subOptions'] as List?)
?.map((sub) => PermissionOption.fromJson(sub))
.toList() ??
[],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'isChecked': isChecked,
'isHighlighted': isHighlighted,
'subOptions': subOptions.map((sub) => sub.toJson()).toList(),
};
}
}
enum CheckState { none, some, all }

View File

@ -0,0 +1,17 @@
class TreeNode {
String uuid;
String title;
bool isChecked;
bool isHighlighted;
bool isExpanded;
List<TreeNode> children;
TreeNode({
required this.uuid,
required this.title,
this.isChecked = false,
this.isHighlighted = false,
this.isExpanded = false,
this.children = const [],
});
}

View File

@ -0,0 +1,359 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class AddNewUserDialog extends StatefulWidget {
const AddNewUserDialog({super.key});
@override
_AddNewUserDialogState createState() => _AddNewUserDialogState();
}
class _AddNewUserDialogState extends State<AddNewUserDialog> {
int currentStep = 1;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => UsersBloc()
..add(const LoadCommunityAndSpacesEvent())
..add(const RoleEvent()),
child: BlocConsumer<UsersBloc, UsersState>(
listener: (context, state) {},
builder: (context, state) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
return Dialog(
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(20))),
width: 900,
child: Column(
children: [
// Title
const Padding(
padding: EdgeInsets.all(8.0),
child: SizedBox(
child: Text(
"Add New User",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: ColorsManager.secondaryColor),
),
),
),
const Divider(),
Expanded(
child: Row(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildStep1Indicator(1, "Basics", _blocRole),
_buildStep2Indicator(2, "Spaces", _blocRole),
_buildStep3Indicator(
3, "Role & Permissions", _blocRole),
],
),
),
),
Container(
width: 1,
color: ColorsManager.grayBorder,
),
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 10),
Expanded(
child: _getFormContent(),
),
const SizedBox(height: 20),
],
),
),
),
],
),
),
const Divider(),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: const Text("Cancel"),
),
InkWell(
onTap: () {
_blocRole.add(const CheckEmailEvent());
setState(() {
if (currentStep < 3) {
currentStep++;
if (currentStep == 2) {
_blocRole.add(
CheckStepStatus(isEditUser: false));
} else if (currentStep == 3) {
_blocRole
.add(const CheckSpacesStepStatus());
}
} else {
_blocRole
.add(SendInviteUsers(context: context));
}
});
},
child: Text(
currentStep < 3 ? "Next" : "Save",
style: TextStyle(
color: (_blocRole.isCompleteSpaces == false ||
_blocRole.isCompleteBasics ==
false ||
_blocRole
.isCompleteRolePermissions ==
false) &&
currentStep == 3
? ColorsManager.grayColor
: ColorsManager.secondaryColor),
),
),
],
),
),
],
),
));
}));
}
Widget _getFormContent() {
switch (currentStep) {
case 1:
return BasicsView(
userId: '',
);
case 2:
return SpacesAccessView();
case 3:
return const RolesAndPermission();
default:
return Container();
}
}
int step3 = 0;
Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) {
return GestureDetector(
onTap: () {
setState(() {
bloc.add(const CheckSpacesStepStatus());
currentStep = step;
Future.delayed(const Duration(milliseconds: 500), () {
bloc.add(ValidateBasicsStep());
});
});
if (step3 == 3) {
bloc.add(const CheckRoleStepStatus());
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
SvgPicture.asset(
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteBasics == false
? Assets.wrongProcessIcon
: bloc.isCompleteBasics == true
? Assets.completeProcessIcon
: Assets.uncomplete_ProcessIcon,
width: 25,
height: 25,
),
const SizedBox(width: 10),
Text(
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
if (step != 3)
Padding(
padding: const EdgeInsets.all(5.0),
child: Padding(
padding: const EdgeInsets.only(left: 12),
child: Container(
height: 60,
width: 1,
color: Colors.grey,
),
),
)
],
),
);
}
Widget _buildStep2Indicator(int step, String label, UsersBloc bloc) {
return GestureDetector(
onTap: () {
setState(() {
currentStep = step;
bloc.add(CheckStepStatus(isEditUser: false));
if (step3 == 3) {
bloc.add(const CheckRoleStepStatus());
}
});
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
SvgPicture.asset(
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteSpaces == false
? Assets.wrongProcessIcon
: bloc.isCompleteSpaces == true
? Assets.completeProcessIcon
: Assets.uncomplete_ProcessIcon,
width: 25,
height: 25,
),
const SizedBox(width: 10),
Text(
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
if (step != 3)
Padding(
padding: const EdgeInsets.all(5.0),
child: Padding(
padding: const EdgeInsets.only(left: 12),
child: Container(
height: 60,
width: 1,
color: Colors.grey,
),
),
)
],
),
);
}
Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) {
return GestureDetector(
onTap: () {
setState(() {
currentStep = step;
step3 = step;
bloc.add(const CheckSpacesStepStatus());
bloc.add(CheckStepStatus(isEditUser: false));
});
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
SvgPicture.asset(
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteRolePermissions == false
? Assets.wrongProcessIcon
: bloc.isCompleteRolePermissions == true
? Assets.completeProcessIcon
: Assets.uncomplete_ProcessIcon,
width: 25,
height: 25,
),
const SizedBox(width: 10),
Text(
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
if (step != 3)
Padding(
padding: const EdgeInsets.all(5.0),
child: Padding(
padding: const EdgeInsets.only(left: 12),
child: Container(
height: 60,
width: 1,
color: Colors.grey,
),
),
)
],
),
);
}
}

View File

@ -0,0 +1,336 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl_phone_field/countries.dart';
import 'package:intl_phone_field/country_picker_dialog.dart';
import 'package:intl_phone_field/intl_phone_field.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class BasicsView extends StatelessWidget {
final String? userId;
const BasicsView({super.key, this.userId = ''});
@override
Widget build(BuildContext context) {
return BlocBuilder<UsersBloc, UsersState>(builder: (context, state) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
if (state is BasicsStepInvalidState) {
_blocRole.formKey.currentState?.validate();
}
return Form(
key: _blocRole.formKey,
child: ListView(
shrinkWrap: true,
children: [
Text(
'Set up the basics',
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 20,
color: Colors.black),
),
const SizedBox(
height: 50,
),
Text(
'To get started, fill out some basic information about who youre adding as a user.',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
),
),
const SizedBox(
height: 35,
),
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
child: Row(
children: [
const Text(
" * ",
style: TextStyle(
color: ColorsManager.red,
fontWeight: FontWeight.w900,
fontSize: 15,
),
),
Text(
'First Name',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 13,
),
),
],
)),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
style:
const TextStyle(color: ColorsManager.blackColor),
onChanged: (value) {
Future.delayed(const Duration(milliseconds: 200),
() {
_blocRole.add(ValidateBasicsStep());
});
},
controller: _blocRole.firstNameController,
decoration: inputTextFormDeco(
hintText: "Enter first name",
).copyWith(
hintStyle: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter first name';
}
return null;
},
),
),
],
),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
child: Row(
children: [
const Text(
" * ",
style: TextStyle(
color: ColorsManager.red,
fontWeight: FontWeight.w900,
fontSize: 15,
),
),
Text('Last Name',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 13,
)),
],
)),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
onChanged: (value) {
Future.delayed(const Duration(milliseconds: 200),
() {
_blocRole.add(ValidateBasicsStep());
});
},
controller: _blocRole.lastNameController,
style: const TextStyle(color: Colors.black),
decoration:
inputTextFormDeco(hintText: "Enter last name")
.copyWith(
hintStyle: context
.textTheme.bodyMedium
?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray)),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter last name';
}
return null;
},
),
),
],
),
),
],
),
const SizedBox(height: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
child: Row(
children: [
const Text(
" * ",
style: TextStyle(
color: ColorsManager.red,
fontWeight: FontWeight.w900,
fontSize: 15,
),
),
Text(
'Email Address',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 13,
),
),
],
)),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
enabled: userId != '' ? false : true,
onChanged: (value) {
Future.delayed(const Duration(milliseconds: 200), () {
_blocRole.add(CheckStepStatus(
isEditUser: userId != '' ? false : true));
_blocRole.add(ValidateBasicsStep());
});
},
controller: _blocRole.emailController,
style: const TextStyle(color: ColorsManager.blackColor),
decoration: inputTextFormDeco(hintText: "name@example.com")
.copyWith(
hintStyle: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter Email Address';
}
final emailRegex = RegExp(
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
);
if (!emailRegex.hasMatch(value)) {
return 'Enter a valid Email Address';
}
if (_blocRole.checkEmailValid != "Valid email") {
return _blocRole.checkEmailValid;
}
return null;
},
),
),
],
),
const SizedBox(height: 10),
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
child: Row(
children: [
Text(
'Mobile Number',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 13,
),
),
],
)),
IntlPhoneField(
pickerDialogStyle: PickerDialogStyle(),
dropdownIconPosition: IconPosition.leading,
disableLengthCheck: true,
dropdownTextStyle:
const TextStyle(color: ColorsManager.blackColor),
textInputAction: TextInputAction.done,
decoration: inputTextFormDeco(
hintText: "05x xxx xxxx",
).copyWith(
hintStyle: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray),
),
initialCountryCode: 'AE',
countries: const [
Country(
name: "United Arab Emirates",
nameTranslations: {
"en": "United Arab Emirates",
"ar": "الإمارات العربية المتحدة",
},
flag: "🇦🇪",
code: "AE",
dialCode: "971",
minLength: 9,
maxLength: 9,
),
Country(
name: "Saudi Arabia",
nameTranslations: {
"en": "Saudi Arabia",
"ar": "السعودية",
},
flag: "🇸🇦",
code: "SA",
dialCode: "966",
minLength: 9,
maxLength: 9,
),
],
style: const TextStyle(color: Colors.black),
controller: _blocRole.phoneController,
)
],
),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
child: Row(
children: [
Text(
'Job Title',
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 13,
),
),
],
)),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: _blocRole.jobTitleController,
style:
const TextStyle(color: ColorsManager.blackColor),
decoration: inputTextFormDeco(
hintText: "Job Title (Optional)")
.copyWith(
hintStyle: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray),
),
keyboardType: TextInputType.phone,
),
),
],
),
),
],
),
const SizedBox(height: 20),
],
),
);
});
}
}

View File

@ -0,0 +1,146 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/tree_node_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class TreeView extends StatelessWidget {
final String? userId;
const TreeView({
super.key,
this.userId,
});
@override
Widget build(BuildContext context) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
debugPrint('TreeView constructed with userId = $userId');
return BlocProvider(
create: (_) => UsersBloc(),
// ..add(const LoadCommunityAndSpacesEvent()),
child: BlocConsumer<UsersBloc, UsersState>(
listener: (context, state) {
// if (state is SpacesLoadedState) {
// _blocRole.add(GetUserByIdEvent(uuid: userId));
// }
},
builder: (context, state) {
if (state is UsersLoadingState) {
return const Center(child: CircularProgressIndicator());
}
return SingleChildScrollView(
child: _buildTree(_blocRole.updatedCommunities, _blocRole),
);
},
),
);
}
Widget _buildTree(
List<TreeNode> nodes,
UsersBloc bloc, {
int level = 0,
}) {
return Column(
children: nodes.map((node) {
return Container(
color: node.isHighlighted ? Colors.blue.shade50 : Colors.transparent,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
/// Checkbox (GestureDetector)
GestureDetector(
onTap: () {
bloc.add(ToggleNodeCheck(node));
},
child: Image.asset(
_getCheckBoxImage(node),
width: 20,
height: 20,
),
),
const SizedBox(width: 15),
Expanded(
child: Padding(
padding: EdgeInsets.only(left: level * 10.0),
child: Row(
children: [
GestureDetector(
onTap: () {
bloc.add(ToggleNodeExpansion(node: node));
},
child: node.children.isNotEmpty
? SvgPicture.asset(
node.isExpanded
? Assets.arrowDown
: Assets.arrowForward,
fit: BoxFit.none,
)
: const SizedBox(width: 16),
),
const SizedBox(width: 20),
Text(
node.title,
style: TextStyle(
fontSize: 16,
color: node.isHighlighted
? ColorsManager.blackColor
: ColorsManager.textGray,
),
),
],
),
),
),
],
),
),
if (node.isExpanded)
_buildTree(
node.children,
bloc,
level: level + 1,
),
],
),
);
}).toList(),
);
}
String _getCheckBoxImage(TreeNode node) {
if (node.children.isEmpty) {
return node.isChecked ? Assets.CheckBoxChecked : Assets.emptyBox;
}
if (_areAllChildrenChecked(node)) {
return Assets.CheckBoxChecked;
} else if (_areSomeChildrenChecked(node)) {
return Assets.rectangleCheckBox;
} else {
return Assets.emptyBox;
}
}
bool _areAllChildrenChecked(TreeNode node) {
return node.children.isNotEmpty &&
node.children.every((child) =>
child.isChecked &&
(child.children.isEmpty || _areAllChildrenChecked(child)));
}
bool _areSomeChildrenChecked(TreeNode node) {
return node.children.isNotEmpty &&
node.children.any((child) =>
child.isChecked ||
(child.children.isNotEmpty && _areSomeChildrenChecked(child)));
}
}

View File

@ -0,0 +1,108 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class DeleteUserDialog extends StatefulWidget {
final Function()? onTapDelete;
DeleteUserDialog({super.key, this.onTapDelete});
@override
_DeleteUserDialogState createState() => _DeleteUserDialogState();
}
class _DeleteUserDialogState extends State<DeleteUserDialog> {
int currentStep = 1;
@override
Widget build(BuildContext context) {
return Dialog(
child: Container(
height: 160,
width: 200,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(20))),
child: Column(
children: [
const Padding(
padding: EdgeInsets.all(8.0),
child: SizedBox(
child: Text(
"Delete User",
style: TextStyle(
color: ColorsManager.red,
fontSize: 18,
fontWeight: FontWeight.bold),
),
),
),
const Padding(
padding: EdgeInsets.only(
left: 25,
right: 25,
),
child: Divider(),
),
const Expanded(
child: Padding(
padding: EdgeInsets.only(left: 25, right: 25, top: 10, bottom: 10),
child: Text(
"Are you sure you want to delete this user?",
textAlign: TextAlign.center,
),
)),
Row(
children: [
Expanded(
child: InkWell(
onTap: () {
Navigator.of(context).pop(true);
},
child: Container(
padding: const EdgeInsets.all(10),
decoration: const BoxDecoration(
border: Border(
right: BorderSide(
color: ColorsManager.grayBorder,
width: 0.5,
),
top: BorderSide(
color: ColorsManager.grayBorder,
width: 1,
),
),
),
child: const Center(child: Text('Cancel'))),
)),
Expanded(
child: InkWell(
onTap: widget.onTapDelete,
child: Container(
padding: const EdgeInsets.all(10),
decoration: const BoxDecoration(
border: Border(
left: BorderSide(
color: ColorsManager.grayBorder,
width: 0.5,
),
top: BorderSide(
color: ColorsManager.grayBorder,
width: 1,
),
),
),
child: const Center(
child: Text(
'Delete',
style: TextStyle(
color: ColorsManager.red,
),
))),
)),
],
)
],
),
));
}
}

View File

@ -0,0 +1,364 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/basics_view.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/roles_and_permission.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/spaces_access_view.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class EditUserDialog extends StatefulWidget {
final String? userId;
const EditUserDialog({super.key, this.userId});
@override
_EditUserDialogState createState() => _EditUserDialogState();
}
class _EditUserDialogState extends State<EditUserDialog> {
int currentStep = 1;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => UsersBloc()
..add(const LoadCommunityAndSpacesEvent())
..add(const RoleEvent())
..add(GetUserByIdEvent(uuid: widget.userId)),
child: BlocConsumer<UsersBloc, UsersState>(listener: (context, state) {
if (state is SpacesLoadedState) {
BlocProvider.of<UsersBloc>(context)
.add(GetUserByIdEvent(uuid: widget.userId));
}
}, builder: (context, state) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
return Dialog(
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(20))),
width: 900,
child: Column(
children: [
// Title
const Padding(
padding: EdgeInsets.all(8.0),
child: SizedBox(
child: Text(
"Edit User",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: ColorsManager.secondaryColor),
),
),
),
const Divider(),
Expanded(
child: Row(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildStep1Indicator(1, "Basics", _blocRole),
_buildStep2Indicator(2, "Spaces", _blocRole),
_buildStep3Indicator(
3, "Role & Permissions", _blocRole),
],
),
),
),
Container(
width: 1,
color: ColorsManager.grayBorder,
),
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 10),
Expanded(
child: _getFormContent(widget.userId),
),
const SizedBox(height: 20),
],
),
),
),
],
),
),
const Divider(),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: const Text("Cancel"),
),
InkWell(
onTap: () {
// _blocRole.add(const CheckEmailEvent());
setState(() {
if (currentStep < 3) {
currentStep++;
if (currentStep == 2) {
_blocRole
.add(CheckStepStatus(isEditUser: true));
} else if (currentStep == 3) {
_blocRole.add(const CheckSpacesStepStatus());
}
} else {
_blocRole.add(EditInviteUsers(
context: context,
userId: widget.userId!));
}
});
},
child: Text(
currentStep < 3 ? "Next" : "Save",
style: TextStyle(
color: (_blocRole.isCompleteSpaces == false ||
_blocRole.isCompleteBasics == false ||
_blocRole.isCompleteRolePermissions ==
false) &&
currentStep == 3
? ColorsManager.grayColor
: ColorsManager.secondaryColor),
),
),
],
),
),
],
),
));
}));
}
Widget _getFormContent(userid) {
switch (currentStep) {
case 1:
return BasicsView(
userId: userid,
);
case 2:
return SpacesAccessView(
userId: userid,
);
case 3:
return const RolesAndPermission();
default:
return Container();
}
}
int step3 = 0;
Widget _buildStep1Indicator(int step, String label, UsersBloc bloc) {
return GestureDetector(
onTap: () {
setState(() {
bloc.add(const CheckSpacesStepStatus());
currentStep = step;
Future.delayed(const Duration(milliseconds: 500), () {
bloc.add(ValidateBasicsStep());
});
});
if (step3 == 3) {
bloc.add(const CheckRoleStepStatus());
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
SvgPicture.asset(
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteBasics == false
? Assets.wrongProcessIcon
: bloc.isCompleteBasics == true
? Assets.completeProcessIcon
: Assets.uncomplete_ProcessIcon,
width: 25,
height: 25,
),
const SizedBox(width: 10),
Text(
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
if (step != 3)
Padding(
padding: const EdgeInsets.all(5.0),
child: Padding(
padding: const EdgeInsets.only(left: 12),
child: Container(
height: 60,
width: 1,
color: Colors.grey,
),
),
)
],
),
);
}
Widget _buildStep2Indicator(int step, String label, UsersBloc bloc) {
return GestureDetector(
onTap: () {
setState(() {
currentStep = step;
bloc.add(CheckStepStatus(isEditUser: true));
if (step3 == 3) {
bloc.add(const CheckRoleStepStatus());
}
});
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
SvgPicture.asset(
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteSpaces == false
? Assets.wrongProcessIcon
: bloc.isCompleteSpaces == true
? Assets.completeProcessIcon
: Assets.uncomplete_ProcessIcon,
width: 25,
height: 25,
),
const SizedBox(width: 10),
Text(
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
if (step != 3)
Padding(
padding: const EdgeInsets.all(5.0),
child: Padding(
padding: const EdgeInsets.only(left: 12),
child: Container(
height: 60,
width: 1,
color: Colors.grey,
),
),
)
],
),
);
}
Widget _buildStep3Indicator(int step, String label, UsersBloc bloc) {
return GestureDetector(
onTap: () {
setState(() {
currentStep = step;
step3 = step;
bloc.add(const CheckSpacesStepStatus());
bloc.add(CheckStepStatus(isEditUser: true));
});
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: [
SvgPicture.asset(
currentStep == step
? Assets.currentProcessIcon
: bloc.isCompleteRolePermissions == false
? Assets.wrongProcessIcon
: bloc.isCompleteRolePermissions == true
? Assets.completeProcessIcon
: Assets.uncomplete_ProcessIcon,
width: 25,
height: 25,
),
const SizedBox(width: 10),
Text(
label,
style: TextStyle(
fontSize: 16,
color: currentStep == step
? ColorsManager.blackColor
: ColorsManager.greyColor,
fontWeight: currentStep == step
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
if (step != 3)
Padding(
padding: const EdgeInsets.all(5.0),
child: Padding(
padding: const EdgeInsets.only(left: 12),
child: Container(
height: 60,
width: 1,
color: Colors.grey,
),
),
)
],
),
);
}
}

View File

@ -0,0 +1,295 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class PermissionManagement extends StatefulWidget {
final UsersBloc? bloc;
const PermissionManagement({Key? key, this.bloc}) : super(key: key);
@override
_PermissionManagementState createState() => _PermissionManagementState();
}
class _PermissionManagementState extends State<PermissionManagement> {
void toggleOptionById(String id) {
setState(() {
for (var mainOption in widget.bloc!.permissions) {
if (mainOption.id == id) {
final isChecked =
checkifOneOfthemChecked(mainOption) == CheckState.all;
mainOption.isChecked = !isChecked;
for (var subOption in mainOption.subOptions) {
subOption.isChecked = !isChecked;
for (var child in subOption.subOptions) {
child.isChecked = !isChecked;
}
}
return;
}
for (var subOption in mainOption.subOptions) {
if (subOption.id == id) {
subOption.isChecked = !subOption.isChecked;
for (var child in subOption.subOptions) {
child.isChecked = subOption.isChecked;
}
mainOption.isChecked =
mainOption.subOptions.every((sub) => sub.isChecked);
return;
}
for (var child in subOption.subOptions) {
if (child.id == id) {
child.isChecked = !child.isChecked;
subOption.isChecked =
subOption.subOptions.every((child) => child.isChecked);
mainOption.isChecked =
mainOption.subOptions.every((sub) => sub.isChecked);
return;
}
}
}
}
});
}
CheckState checkifOneOfthemChecked(PermissionOption mainOption) {
bool allSelected = true;
bool someSelected = false;
for (var subOption in mainOption.subOptions) {
if (subOption.isChecked) {
someSelected = true;
} else {
allSelected = false;
}
for (var child in subOption.subOptions) {
if (child.isChecked) {
someSelected = true;
} else {
allSelected = false;
}
}
}
if (allSelected) {
return CheckState.all;
} else if (someSelected) {
return CheckState.some;
} else {
return CheckState.none;
}
}
@override
Widget build(BuildContext context) {
return ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: widget.bloc!.permissions.length,
itemBuilder: (context, index) {
final option = widget.bloc!.permissions[index];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
InkWell(
// onTap: () => toggleOptionById(option.id),
child: Builder(
builder: (context) {
final checkState = checkifOneOfthemChecked(option);
if (checkState == CheckState.all) {
return Image.asset(
Assets.CheckBoxChecked,
width: 20,
height: 20,
);
} else if (checkState == CheckState.some) {
return Image.asset(
Assets.rectangleCheckBox,
width: 20,
height: 20,
);
} else {
return Image.asset(
Assets.emptyBox,
width: 20,
height: 20,
);
}
},
),
),
const SizedBox(width: 8),
Text(
option.title,
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 12,
color: ColorsManager.blackColor),
),
],
),
const SizedBox(
height: 10,
),
...option.subOptions.map((subOption) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: option.isHighlighted
? Colors.blue.shade50
: Colors.white,
child: Row(
children: [
InkWell(
// onTap: () => toggleOptionById(subOption.id),
child: Builder(
builder: (context) {
final checkState =
checkifOneOfthemChecked(PermissionOption(
id: subOption.id,
title: subOption.title,
subOptions: [subOption],
));
if (checkState == CheckState.all) {
return Image.asset(
Assets.CheckBoxChecked,
width: 20,
height: 20,
);
} else if (checkState == CheckState.some) {
return Image.asset(
Assets.rectangleCheckBox,
width: 20,
height: 20,
);
} else {
return Image.asset(
Assets.emptyBox,
width: 20,
height: 20,
);
}
},
),
),
const SizedBox(width: 8),
Text(
subOption.title,
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 12,
color: ColorsManager.lightGreyColor),
),
],
),
),
Padding(
padding: const EdgeInsets.only(left: 50.0),
child: GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 2.0,
crossAxisSpacing: 0.2,
childAspectRatio: 5,
),
itemCount: subOption.subOptions.length,
itemBuilder: (context, index) {
final child = subOption.subOptions[index];
return Container(
color: option.isHighlighted
? Colors.blue.shade50
: Colors.white,
child: Row(
children: [
Builder(
builder: (context) {
final checkState =
checkifOneOfthemChecked(PermissionOption(
id: child.id,
title: child.title,
subOptions: [child],
));
if (checkState == CheckState.all) {
return Image.asset(
Assets.CheckBoxChecked,
width: 20,
height: 20,
);
} else if (checkState == CheckState.some) {
return Image.asset(
Assets.rectangleCheckBox,
width: 20,
height: 20,
);
} else {
return Image.asset(
Assets.emptyBox,
width: 20,
height: 20,
);
}
},
),
const SizedBox(width: 8),
Text(
child.title,
style: context.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.lightGreyColor),
),
],
),
);
},
),
)
],
);
}).toList(),
],
);
},
);
}
}
// Container(
// height: 50,
// width: 120,
// child: CheckboxListTile(
// activeColor: ColorsManager.dialogBlueTitle,
// selectedTileColor: child.isHighlighted
// ? Colors.blue.shade50
// : Colors.white,
// dense: true,
// controlAffinity:
// ListTileControlAffinity.leading,
// title: Text(
// child.title,
// style: context.textTheme.bodyMedium?.copyWith(
// fontWeight: FontWeight.w400,
// fontSize: 12,
// color: ColorsManager.lightGreyColor),
// ),
// value: child.isChecked,
// onChanged: (value) =>
// toggleOptionById(child.id),
// enabled: false,
// ),
// ),

View File

@ -0,0 +1,147 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart';
Future<void> showPopUpFilterMenu({
required BuildContext context,
Function()? onSortAtoZ,
Function()? onSortZtoA,
Function()? cancelButton,
required Map<String, bool> checkboxStates,
required RelativeRect position,
Function()? onOkPressed,
List<String>? list,
}) async {
await showMenu(
context: context,
position:position,
color: ColorsManager.whiteColors,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
items: <PopupMenuEntry>[
PopupMenuItem(
onTap: onSortAtoZ,
child: ListTile(
leading: Image.asset(
Assets.AtoZIcon,
width: 25,
),
title: const Text(
"Sort A to Z",
style: TextStyle(color: Colors.blueGrey),
),
),
),
PopupMenuItem(
onTap: onSortZtoA,
child: ListTile(
leading: Image.asset(
Assets.ZtoAIcon,
width: 25,
),
title: const Text(
"Sort Z to A",
style: TextStyle(color: Colors.blueGrey),
),
),
),
const PopupMenuDivider(),
const PopupMenuItem(
child: Text(
"Filter by Status",
style: TextStyle(fontWeight: FontWeight.bold),
)
// Container(
// decoration: containerDecoration.copyWith(
// boxShadow: [],
// borderRadius: const BorderRadius.only(
// topLeft: Radius.circular(10), topRight: Radius.circular(10))),
// child: Padding(
// padding: const EdgeInsets.all(8.0),
// child: TextFormField(
// onChanged: onTextFieldChanged,
// style: const TextStyle(color: Colors.black),
// decoration: textBoxDecoration(radios: 15)!.copyWith(
// fillColor: ColorsManager.whiteColors,
// errorStyle: const TextStyle(height: 0),
// hintStyle: context.textTheme.titleSmall?.copyWith(
// color: Colors.grey,
// fontSize: 12,
// ),
// hintText: 'Search',
// suffixIcon: SizedBox(
// child: SvgPicture.asset(
// Assets.searchIconUser,
// fit: BoxFit.none,
// ),
// ),
// ),
// // "Filter by Status",
// // style: TextStyle(fontWeight: FontWeight.bold),
// ),
// ),
// ),
),
PopupMenuItem(
child: Container(
decoration: containerDecoration.copyWith(
boxShadow: [],
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10))),
padding: const EdgeInsets.all(10),
height: 200,
width: 400,
child: Container(
padding: const EdgeInsets.all(10),
color: Colors.white,
child: ListView.builder(
itemCount: list?.length ?? 0,
itemBuilder: (context, index) {
final item = list![index];
return CheckboxListTile(
dense: true,
title: Text(item),
value: checkboxStates[item],
onChanged: (bool? newValue) {
checkboxStates[item] = newValue ?? false;
(context as Element).markNeedsBuild();
},
);
},
),
),
),
),
PopupMenuItem(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
Navigator.of(context).pop(); // Close the menu
},
child: const Text("Cancel"),
),
GestureDetector(
onTap: onOkPressed,
child: const Text(
"OK",
style: TextStyle(
color: ColorsManager.spaceColor,
),
),
),
],
),
),
],
);
}

View File

@ -0,0 +1,115 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/style.dart';
class RoleDropdown extends StatefulWidget {
final UsersBloc? bloc;
const RoleDropdown({super.key, this.bloc});
@override
_RoleDropdownState createState() => _RoleDropdownState();
}
class _RoleDropdownState extends State<RoleDropdown> {
late String selectedRole;
@override
void initState() {
super.initState();
selectedRole = widget.bloc!.roleSelected;
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Text(
" * ",
style: TextStyle(
color: ColorsManager.red,
fontWeight: FontWeight.w900,
fontSize: 15,
),
),
Text(
"Role",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: Colors.black,
),
),
],
),
const SizedBox(height: 8),
SizedBox(
child: DropdownButtonFormField<String>(
dropdownColor: ColorsManager.whiteColors,
alignment: Alignment.center,
focusColor: Colors.white,
autofocus: true,
value: selectedRole.isNotEmpty ? selectedRole : null,
items: widget.bloc!.roles.map((role) {
return DropdownMenuItem<String>(
value: role.uuid,
child: Text(role.type),
);
}).toList(),
onChanged: (value) {
setState(() {
selectedRole = value!;
});
widget.bloc!.roleSelected = selectedRole;
widget.bloc!
.add(PermissionEvent(roleUuid: widget.bloc!.roleSelected));
},
icon: const SizedBox.shrink(),
borderRadius: const BorderRadius.all(Radius.circular(10)),
hint: const Padding(
padding: EdgeInsets.only(left: 10),
child: Text(
"Please Select",
style: TextStyle(
color: ColorsManager.textGray,
),
),
),
decoration: inputTextFormDeco().copyWith(
contentPadding: EdgeInsets.zero,
suffixIcon: Container(
padding: EdgeInsets.zero,
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 Center(
child: Icon(
Icons.keyboard_arrow_down,
color: ColorsManager.textGray,
),
),
),
),
),
),
],
),
);
}
}

View File

@ -0,0 +1,125 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/permission_management.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/role_dropdown.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class RolesAndPermission extends StatelessWidget {
const RolesAndPermission({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<UsersBloc, UsersState>(builder: (context, state) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
return Container(
color: Colors.white,
child: Form(
key: _blocRole.formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'Role & Permissions',
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 20,
color: Colors.black),
),
const SizedBox(
height: 15,
),
SizedBox(
width: 350,
height: 100,
child: RoleDropdown(
bloc: _blocRole,
)),
const SizedBox(height: 10),
Expanded(
child: SizedBox(
child: Column(
children: [
Expanded(
flex: 2,
child: Container(
decoration: const BoxDecoration(
color: ColorsManager.circleRolesBackground,
borderRadius: BorderRadius.only(
topRight: Radius.circular(20),
topLeft: Radius.circular(20)),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(20)),
border: Border.all(
color: ColorsManager.grayBorder)),
child: TextFormField(
style:
const TextStyle(color: Colors.black),
controller:
_blocRole.roleSearchController,
onChanged: (value) {
_blocRole.add(SearchPermission(
nodes: _blocRole.permissions,
searchTerm: value));
},
decoration: textBoxDecoration(radios: 20)!
.copyWith(
fillColor: Colors.white,
suffixIcon: Padding(
padding:
const EdgeInsets.only(right: 16),
child: SvgPicture.asset(
Assets.textFieldSearch,
width: 24,
height: 24,
),
),
hintStyle: context.textTheme.bodyMedium
?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray),
),
),
),
),
],
),
),
),
),
Expanded(
flex: 7,
child: Container(
color: ColorsManager.circleRolesBackground,
padding: const EdgeInsets.all(8.0),
child: Container(
color: ColorsManager.whiteColors,
child: PermissionManagement(
bloc: _blocRole,
))))
],
),
),
),
],
),
),
);
});
}
}

View File

@ -0,0 +1,122 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/bloc/users_status.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/build_tree_view.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class SpacesAccessView extends StatelessWidget {
final String? userId;
const SpacesAccessView({super.key, this.userId = ''});
@override
Widget build(BuildContext context) {
return BlocBuilder<UsersBloc, UsersState>(builder: (context, state) {
final _blocRole = BlocProvider.of<UsersBloc>(context);
return Container(
color: Colors.white,
child: Form(
key: _blocRole.formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'Spaces access',
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w700,
fontSize: 20,
color: Colors.black),
),
const SizedBox(
height: 35,
),
const SizedBox(
child: Text(
'Select the spaces you would like to grant access to for the user you are adding'),
),
const SizedBox(
height: 25,
),
Expanded(
child: SizedBox(
child: Column(
children: [
Expanded(
flex: 2,
child: Container(
decoration: const BoxDecoration(
color: ColorsManager.circleRolesBackground,
borderRadius: BorderRadius.only(
topRight: Radius.circular(20),
topLeft: Radius.circular(20)),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(20)),
border: Border.all(
color: ColorsManager.grayBorder)),
child: TextFormField(
style:
const TextStyle(color: Colors.black),
// controller: _blocRole.firstNameController,
onChanged: (value) {
_blocRole.add(SearchAnode(
nodes: _blocRole.updatedCommunities,
searchTerm: value));
},
decoration: textBoxDecoration(radios: 20)!
.copyWith(
fillColor: Colors.white,
suffixIcon: Padding(
padding:
const EdgeInsets.only(right: 16),
child: SvgPicture.asset(
Assets.textFieldSearch,
width: 24,
height: 24,
),
),
hintStyle: context.textTheme.bodyMedium
?.copyWith(
fontWeight: FontWeight.w400,
fontSize: 12,
color: ColorsManager.textGray),
),
),
),
),
],
),
),
),
),
Expanded(
flex: 7,
child: Container(
color: ColorsManager.circleRolesBackground,
padding: const EdgeInsets.all(8.0),
child: Container(
color: ColorsManager.whiteColors,
child: TreeView(userId: userId))))
],
),
),
),
],
),
),
);
});
}
}

View File

@ -0,0 +1,320 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart';
import 'package:syncrow_web/services/user_permission.dart';
class UserTableBloc extends Bloc<UserTableEvent, UserTableState> {
UserTableBloc() : super(TableInitial()) {
on<GetUsers>(_getUsers);
on<ChangeUserStatus>(_changeUserStatus);
on<SortUsersByNameAsc>(_toggleSortUsersByNameAsc);
on<SortUsersByNameDesc>(_toggleSortUsersByNameDesc);
on<DateOldestToNewestEvent>(_toggleSortUsersByDateOldestToNewest);
on<DateNewestToOldestEvent>(_toggleSortUsersByDateNewestToOldest);
on<SearchUsers>(_searchUsers);
on<ChangePage>(_handlePageChange);
on<FilterUsersByRoleEvent>(_filterUsersByRole);
on<FilterUsersByJobEvent>(_filterUsersByJobTitle);
on<FilterUsersByCreatedEvent>(_filterUsersByCreated);
on<FilterUsersByDeActevateEvent>(_filterUserStatus);
on<DeleteUserEvent>(_deleteUser);
on<FilterClearEvent>(_filterClear);
}
int itemsPerPage = 20;
int currentPage = 1;
List<RolesUserModel> users = [];
List<RolesUserModel> initialUsers = [];
String currentSortOrder = '';
String currentSortOrderDate = '';
List<String> roleTypes = [];
List<String> jobTitle = [];
List<String> createdBy = [];
List<String> status = ['active', 'invited', 'disabled'];
Future<void> _getUsers(GetUsers event, Emitter<UserTableState> emit) async {
emit(UsersLoadingState());
try {
roleTypes.clear();
jobTitle.clear();
createdBy.clear();
// deActivate.clear();
users = await UserPermissionApi().fetchUsers();
users.sort((a, b) {
final dateA = _parseDateTime(a.createdDate);
final dateB = _parseDateTime(b.createdDate);
return dateB.compareTo(dateA);
});
for (var user in users) {
roleTypes.add(user.roleType.toString());
}
for (var user in users) {
jobTitle.add(user.jobTitle.toString());
}
for (var user in users) {
createdBy.add(user.invitedBy.toString());
}
// for (var user in users) {
// deActivate.add(user.status.toString());
// }
initialUsers = List.from(users);
roleTypes = roleTypes.toSet().toList();
jobTitle = jobTitle.toSet().toList();
createdBy = createdBy.toSet().toList();
// deActivate = deActivate.toSet().toList();
_handlePageChange(ChangePage(1), emit);
emit(UsersLoadedState(users: users));
} catch (e) {
emit(ErrorState(e.toString()));
}
}
Future<void> _deleteUser(
DeleteUserEvent event, Emitter<UserTableState> emit) async {
emit(UsersLoadingState());
try {
bool res = await UserPermissionApi().deleteUserById(event.userId);
if (res == true) {
Navigator.of(event.context).pop(true);
} else {
emit(const ErrorState('Something error'));
}
emit(UsersLoadedState(users: users));
} catch (e) {
emit(ErrorState(e.toString()));
}
}
Future<void> _changeUserStatus(
ChangeUserStatus event, Emitter<UserTableState> emit) async {
try {
emit(UsersLoadingState());
bool res = await UserPermissionApi().changeUserStatusById(
event.userId, event.newStatus == "disabled" ? true : false);
if (res == true) {
add(const GetUsers());
// users = users.map((user) {
// if (user.uuid == event.userId) {
// return RolesUserModel(
// uuid: user.uuid,
// createdAt: user.createdAt,
// email: user.email,
// firstName: user.firstName,
// lastName: user.lastName,
// roleType: user.roleType,
// status: event.newStatus,
// isEnabled: event.newStatus == "disabled" ? false : true,
// invitedBy: user.invitedBy,
// phoneNumber: user.phoneNumber,
// jobTitle: user.jobTitle,
// createdDate: user.createdDate,
// createdTime: user.createdTime,
// );
// }
// return user;
// }).toList();
}
emit(UsersLoadedState(users: users));
} catch (e) {
emit(ErrorState(e.toString()));
}
}
void _toggleSortUsersByNameAsc(
SortUsersByNameAsc event, Emitter<UserTableState> emit) {
if (currentSortOrder == "Asc") {
emit(UsersLoadingState());
currentSortOrder = "";
users = List.from(users);
emit(UsersLoadedState(users: users));
} else {
emit(UsersLoadingState());
currentSortOrder = "Asc";
users.sort((a, b) => a.firstName!.compareTo(b.firstName!));
emit(UsersLoadedState(users: users));
}
}
void _toggleSortUsersByNameDesc(
SortUsersByNameDesc event, Emitter<UserTableState> emit) {
if (currentSortOrder == "Desc") {
emit(UsersLoadingState());
currentSortOrder = "";
users = List.from(initialUsers); // Reset to saved initial state
emit(UsersLoadedState(users: users));
} else {
// Sort descending
emit(UsersLoadingState());
currentSortOrder = "Desc";
users.sort((a, b) => b.firstName!.compareTo(a.firstName!));
emit(UsersLoadedState(users: users));
}
}
void _toggleSortUsersByDateNewestToOldest(
DateNewestToOldestEvent event, Emitter<UserTableState> emit) {
if (currentSortOrderDate == "NewestToOldest") {
emit(UsersLoadingState());
currentSortOrder = "";
currentSortOrderDate = "";
users = List.from(initialUsers);
emit(UsersLoadedState(users: users));
} else {
emit(UsersLoadingState());
users.sort((a, b) {
final dateA = _parseDateTime(a.createdDate);
final dateB = _parseDateTime(b.createdDate);
return dateB.compareTo(dateA);
});
emit(UsersLoadedState(users: users));
}
}
void _toggleSortUsersByDateOldestToNewest(
DateOldestToNewestEvent event, Emitter<UserTableState> emit) {
if (currentSortOrderDate == "OldestToNewest") {
emit(UsersLoadingState());
currentSortOrder = "";
currentSortOrderDate = "";
users = List.from(initialUsers);
emit(UsersLoadedState(users: users));
} else {
emit(UsersLoadingState());
users.sort((a, b) {
final dateA = _parseDateTime(a.createdDate);
final dateB = _parseDateTime(b.createdDate);
return dateA.compareTo(dateB);
});
emit(UsersLoadedState(users: users));
}
}
DateTime _parseDateTime(String date) {
try {
final dateParts = date.split('/');
final day = int.parse(dateParts[0]);
final month = int.parse(dateParts[1]);
final year = int.parse(dateParts[2]);
return DateTime(year, month, day);
} catch (e) {
throw FormatException('Invalid date or time format: $date ');
}
}
Future<void> _searchUsers(
SearchUsers event, Emitter<UserTableState> emit) async {
try {
final query = event.query.toLowerCase();
final filteredUsers = initialUsers.where((user) {
final fullName = "${user.firstName} ${user.lastName}".toLowerCase();
final email = user.email.toLowerCase() ;
return fullName.contains(query) || email.contains(query);
}).toList();
emit(UsersLoadedState(users: filteredUsers));
} catch (e) {
emit(ErrorState(e.toString()));
}
}
void _paginateUsers(
int pageNumber, int itemsPerPage, Emitter<UserTableState> emit) {
final startIndex = (pageNumber - 1) * itemsPerPage;
final endIndex = startIndex + itemsPerPage;
if (startIndex >= users.length) {
emit(const UsersLoadedState(users: []));
return;
}
final paginatedUsers = users.sublist(
startIndex,
endIndex > users.length ? users.length : endIndex,
);
emit(UsersLoadedState(users: paginatedUsers));
}
void _handlePageChange(ChangePage event, Emitter<UserTableState> emit) {
const itemsPerPage = 10;
final startIndex = (event.pageNumber - 1) * itemsPerPage;
final endIndex = startIndex + itemsPerPage;
if (startIndex >= users.length) {
emit(const UsersLoadedState(users: []));
return;
}
final paginatedUsers = users.sublist(
startIndex,
endIndex > users.length ? users.length : endIndex,
);
emit(UsersLoadedState(users: paginatedUsers));
}
Set<String> selectedRoles = {};
Set<String> selectedJobTitles = {};
Set<String> selectedCreatedBy = {};
Set<String> selectedStatuses = {};
void _filterUsersByRole(
FilterUsersByRoleEvent event, Emitter<UserTableState> emit) {
selectedRoles = event.selectedRoles.toSet();
final filteredUsers = initialUsers.where((user) {
if (selectedRoles.isEmpty) return true;
return selectedRoles.contains(user.roleType);
}).toList();
emit(UsersLoadedState(users: filteredUsers));
}
void _filterUsersByJobTitle(
FilterUsersByJobEvent event, Emitter<UserTableState> emit) {
selectedJobTitles = event.selectedJob.toSet();
final filteredUsers = initialUsers.where((user) {
if (selectedJobTitles.isEmpty) return true;
return selectedJobTitles.contains(user.jobTitle);
}).toList();
emit(UsersLoadedState(users: filteredUsers));
}
void _filterUsersByCreated(
FilterUsersByCreatedEvent event, Emitter<UserTableState> emit) {
selectedCreatedBy = event.selectedCreatedBy.toSet();
final filteredUsers = initialUsers.where((user) {
if (selectedCreatedBy.isEmpty) return true;
return selectedCreatedBy.contains(user.invitedBy);
}).toList();
emit(UsersLoadedState(users: filteredUsers));
}
void _filterUserStatus(
FilterUsersByDeActevateEvent event, Emitter<UserTableState> emit) {
selectedStatuses = event.selectedActivate.toSet();
final filteredUsers = initialUsers.where((user) {
if (selectedStatuses.isEmpty) return true;
return selectedStatuses.contains(user.status);
}).toList();
emit(UsersLoadedState(users: filteredUsers));
}
void _resetAllFilters(Emitter<UserTableState> emit) {
selectedRoles.clear();
selectedJobTitles.clear();
selectedCreatedBy.clear();
selectedStatuses.clear();
emit(UsersLoadedState(users: initialUsers));
}
void _filterClear(FilterClearEvent event, Emitter<UserTableState> emit) {
selectedRoles.clear();
selectedJobTitles.clear();
selectedCreatedBy.clear();
selectedStatuses.clear();
emit(UsersLoadedState(users: initialUsers));
}
}

View File

@ -0,0 +1,137 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';
sealed class UserTableEvent extends Equatable {
const UserTableEvent();
}
class GetRoles extends UserTableEvent {
const GetRoles();
@override
List<Object?> get props => [];
}
class GetUsers extends UserTableEvent {
const GetUsers();
@override
List<Object?> get props => [];
}
class ChangeUserStatus extends UserTableEvent {
final String userId;
final String newStatus;
const ChangeUserStatus({required this.userId, required this.newStatus});
@override
List<Object?> get props => [userId, newStatus];
}
class SortUsersByNameAsc extends UserTableEvent {
const SortUsersByNameAsc();
@override
List<Object?> get props => [];
}
class SortUsersByNameDesc extends UserTableEvent {
const SortUsersByNameDesc();
@override
List<Object?> get props => [];
}
class StoreUsersEvent extends UserTableEvent {
const StoreUsersEvent();
@override
List<Object?> get props => [];
}
class DateNewestToOldestEvent extends UserTableEvent {
const DateNewestToOldestEvent();
@override
List<Object?> get props => [];
}
class DateOldestToNewestEvent extends UserTableEvent {
const DateOldestToNewestEvent();
@override
List<Object?> get props => [];
}
class SearchUsers extends UserTableEvent {
final String query;
SearchUsers(this.query);
@override
List<Object?> get props => [];
}
class ChangePage extends UserTableEvent {
final int pageNumber;
ChangePage(this.pageNumber);
@override
List<Object> get props => [pageNumber];
}
class DeleteUserEvent extends UserTableEvent {
final String userId;
final BuildContext context;
const DeleteUserEvent(this.userId, this.context);
@override
List<Object> get props => [userId, context];
}
class FilterUsersByRoleEvent extends UserTableEvent {
final List<String> selectedRoles;
FilterUsersByRoleEvent(this.selectedRoles);
@override
List<Object?> get props => [selectedRoles];
}
class FilterUsersByJobEvent extends UserTableEvent {
final List<String> selectedJob;
FilterUsersByJobEvent(this.selectedJob);
@override
List<Object?> get props => [selectedJob];
}
class FilterUsersByCreatedEvent extends UserTableEvent {
final List<String> selectedCreatedBy;
FilterUsersByCreatedEvent(this.selectedCreatedBy);
@override
List<Object?> get props => [selectedCreatedBy];
}
class FilterUsersByDeActevateEvent extends UserTableEvent {
final List<String> selectedActivate;
FilterUsersByDeActevateEvent(this.selectedActivate);
@override
List<Object?> get props => [selectedActivate];
}
class FilterOptionsEvent extends UserTableEvent {
final String query;
final List<String> fullOptions;
FilterOptionsEvent({required this.query, required this.fullOptions});
@override
List<Object?> get props => [query, fullOptions];
}
class FilterClearEvent extends UserTableEvent {
FilterClearEvent();
@override
List<Object?> get props => [];
}

View File

@ -0,0 +1,91 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart';
sealed class UserTableState extends Equatable {
const UserTableState();
}
final class TableInitial extends UserTableState {
@override
List<Object> get props => [];
}
final class RolesLoadingState extends UserTableState {
@override
List<Object> get props => [];
}
final class UsersLoadingState extends UserTableState {
@override
List<Object> get props => [];
}
final class RolesLoadedState extends UserTableState {
@override
List<Object> get props => [];
}
final class UsersLoadedState extends UserTableState {
final List<RolesUserModel> users;
const UsersLoadedState({required this.users});
@override
List<Object> get props => [users];
}
final class ErrorState extends UserTableState {
final String message;
const ErrorState(this.message);
@override
List<Object> get props => [message];
}
/// report state
final class SosReportLoadingState extends UserTableState {
@override
List<Object> get props => [];
}
final class RolesErrorState extends UserTableState {
final String message;
const RolesErrorState(this.message);
@override
List<Object> get props => [message];
}
/// automation reports
final class SosAutomationReportLoadingState extends UserTableState {
@override
List<Object> get props => [];
}
final class SosAutomationReportErrorState extends UserTableState {
final String message;
const SosAutomationReportErrorState(this.message);
@override
List<Object> get props => [message];
}
final class ChangeTapStatus extends UserTableState {
final bool select;
const ChangeTapStatus({required this.select});
@override
List<Object> get props => [select];
}
class FilterOptionsState extends UserTableState {
final List<String> filteredOptions;
const FilterOptionsState(this.filteredOptions);
@override
List<Object> get props => [filteredOptions];
}

View File

@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
Future<void> showDateFilterMenu({
required BuildContext context,
Function()? aToZTap,
Function()? zToaTap,
String? isSelected,
}) async {
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB(
overlay.size.width / 2,
240,
0,
overlay.size.height,
),
Offset.zero & overlay.size,
);
await showMenu(
context: context,
position: position,
color: ColorsManager.whiteColors,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
),
),
items: <PopupMenuEntry>[
PopupMenuItem(
onTap: aToZTap,
child: ListTile(
leading: Image.asset(
Assets.AtoZIcon,
width: 25,
),
title: Text(
"Sort from newest to oldest",
// style: context.textTheme.bodyMedium,
style: TextStyle(
color: isSelected == "NewestToOldest"
? Colors.black
: Colors.blueGrey),
),
),
),
PopupMenuItem(
onTap: zToaTap,
child: ListTile(
leading: Image.asset(
Assets.ZtoAIcon,
width: 25,
),
title: Text(
"Sort from oldest to newest",
style: TextStyle(
color: isSelected == "OldestToNewest"
? Colors.black
: Colors.blueGrey),
),
),
),
],
).then((value) {
// setState(() {
// _isDropdownOpen = false;
// });
});
}

View File

@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
Future<void> showDeActivateFilterMenu({
required BuildContext context,
Function()? aToZTap,
Function()? zToaTap,
String? isSelected,
}) async {
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB(
overlay.size.width / 2,
240,
0,
overlay.size.height,
),
Offset.zero & overlay.size,
);
await showMenu(
context: context,
position: position,
color: ColorsManager.whiteColors,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
),
),
items: <PopupMenuEntry>[
PopupMenuItem(
onTap: aToZTap,
child: ListTile(
leading: Image.asset(
Assets.AtoZIcon,
width: 25,
),
title: Text(
"Sort A to Z",
// style: context.textTheme.bodyMedium,
style: TextStyle(
color: isSelected == "NewestToOldest"
? Colors.black
: Colors.blueGrey),
),
),
),
PopupMenuItem(
onTap: zToaTap,
child: ListTile(
leading: Image.asset(
Assets.ZtoAIcon,
width: 25,
),
title: Text(
"Sort Z to A",
style: TextStyle(
color: isSelected == "OldestToNewest"
? Colors.black
: Colors.blueGrey),
),
),
),
],
).then((value) {
// setState(() {
// _isDropdownOpen = false;
// });
});
}

View File

@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
Future<void> showNameMenu({
required BuildContext context,
Function()? aToZTap,
Function()? zToaTap,
String? isSelected,
}) async {
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB(
overlay.size.width / 25,
240,
0,
overlay.size.height,
),
Offset.zero & overlay.size,
);
await showMenu(
context: context,
position: position,
color: ColorsManager.whiteColors,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
),
),
items: <PopupMenuEntry>[
PopupMenuItem(
onTap: aToZTap,
child: ListTile(
leading: Image.asset(
Assets.AtoZIcon,
width: 25,
),
title: Text(
"Sort A to Z",
// style: context.textTheme.bodyMedium,
style: TextStyle(
color: isSelected == "Asc" ? Colors.black : Colors.blueGrey),
),
),
),
PopupMenuItem(
onTap: zToaTap,
child: ListTile(
leading: Image.asset(
Assets.ZtoAIcon,
width: 25,
),
title: Text(
"Sort Z to A",
style: TextStyle(
color: isSelected == "Desc" ? Colors.black : Colors.blueGrey),
),
),
),
],
).then((value) {
// setState(() {
// _isDropdownOpen = false;
// });
});
}

View File

@ -0,0 +1,356 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/style.dart';
class DynamicTableScreen extends StatefulWidget {
final List<String> titles;
final List<List<Widget>> rows;
final void Function(int columnIndex)? onFilter;
DynamicTableScreen(
{required this.titles, required this.rows, required this.onFilter});
@override
_DynamicTableScreenState createState() => _DynamicTableScreenState();
}
class _DynamicTableScreenState extends State<DynamicTableScreen>
with WidgetsBindingObserver {
late List<double> columnWidths;
// @override
// void initState() {
// super.initState();
// // Initialize column widths with default sizes proportional to the screen width
// // Assigning placeholder values here. The actual sizes will be updated in `build`.
// }
@override
void initState() {
super.initState();
setState(() {
columnWidths = List<double>.filled(widget.titles.length, 150.0);
});
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeMetrics() {
super.didChangeMetrics();
// Screen size might have changed
final newScreenWidth = MediaQuery.of(context).size.width;
setState(() {
columnWidths = List<double>.generate(widget.titles.length, (index) {
if (index == 1) {
return newScreenWidth *
0.12; // 20% of screen width for the second column
} else if (index == 9) {
return newScreenWidth *
0.2; // 25% of screen width for the tenth column
}
return newScreenWidth *
0.09; // Default to 10% of screen width for other columns
});
});
}
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
// Initialize column widths if they are still set to placeholder values
if (columnWidths.every((width) => width == 120.0)) {
columnWidths = List<double>.generate(widget.titles.length, (index) {
if (index == 1) {
return screenWidth * 0.11;
} else if (index == 9) {
return screenWidth * 0.2;
}
return screenWidth * 0.11;
});
setState(() {});
}
return Container(
child: SingleChildScrollView(
clipBehavior: Clip.none,
scrollDirection: Axis.horizontal,
child: Container(
decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.all(Radius.circular(20))),
child: FittedBox(
child: Column(
children: [
// Header Row with Resizable Columns
Container(
width: MediaQuery.of(context).size.width,
decoration: containerDecoration.copyWith(
color: ColorsManager.circleRolesBackground,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15))),
child: Row(
children: List.generate(widget.titles.length, (index) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
FittedBox(
child: Container(
padding: const EdgeInsets.only(left: 5, right: 5),
width: columnWidths[index],
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
child: Text(
widget.titles[index],
maxLines: 2,
style: const TextStyle(
overflow: TextOverflow.ellipsis,
fontWeight: FontWeight.w400,
fontSize: 13,
color: ColorsManager.grayColor,
),
),
),
if (index != 1 &&
index != 9 &&
index != 8 &&
index != 5)
FittedBox(
child: IconButton(
icon: SvgPicture.asset(
Assets.filterTableIcon,
fit: BoxFit.none,
),
onPressed: () {
if (widget.onFilter != null) {
widget.onFilter!(index);
}
},
),
)
],
),
),
),
GestureDetector(
onHorizontalDragUpdate: (details) {
setState(() {
columnWidths[index] = (columnWidths[index] +
details.delta.dx)
.clamp(
150.0, 300.0); // Minimum & Maximum size
});
},
child: MouseRegion(
cursor: SystemMouseCursors
.resizeColumn, // Set the cursor to resize
child: Container(
color: Colors.green,
child: Container(
color: ColorsManager.boxDivider,
width: 1,
height: 50, // Height of the header cell
),
),
),
),
],
);
}),
),
),
// Data Rows with Dividers
widget.rows.isEmpty
? Container(
child: SizedBox(
height: MediaQuery.of(context).size.height / 2,
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
SvgPicture.asset(Assets.emptyTable),
const SizedBox(
height: 15,
),
const Text(
'No Users',
style: TextStyle(
color: ColorsManager.lightGrayColor,
fontSize: 16,
fontWeight: FontWeight.w700),
)
],
),
],
),
),
),
)
: Center(
child: Container(
// height: MediaQuery.of(context).size.height * 0.59,
width: MediaQuery.of(context).size.width,
decoration: containerDecoration.copyWith(
color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15))),
child: ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: widget.rows.length,
itemBuilder: (context, rowIndex) {
if (columnWidths
.every((width) => width == 120.0)) {
columnWidths = List<double>.generate(
widget.titles.length, (index) {
if (index == 1) {
return screenWidth * 0.11;
} else if (index == 9) {
return screenWidth * 0.2;
}
return screenWidth * 0.11;
});
setState(() {});
}
final row = widget.rows[rowIndex];
return Column(
children: [
Container(
child: Padding(
padding: const EdgeInsets.only(
left: 5,
top: 10,
right: 5,
bottom: 10),
child: Row(
children:
List.generate(row.length, (index) {
return SizedBox(
width: columnWidths[index],
child: SizedBox(
child: Padding(
padding: const EdgeInsets.only(
left: 15, right: 10),
child: row[index],
),
),
);
}),
),
),
),
if (rowIndex < widget.rows.length - 1)
Row(
children: List.generate(
widget.titles.length, (index) {
return SizedBox(
width: columnWidths[index],
child: const Divider(
color: ColorsManager.boxDivider,
thickness: 1,
height: 1,
),
);
}),
),
],
);
},
),
),
),
],
),
),
),
),
);
}
}
// Widget build(BuildContext context) {
// return Scaffold(
// body: SingleChildScrollView(
// scrollDirection: Axis.horizontal,
// child: SingleChildScrollView(
// scrollDirection: Axis.vertical,
// child: Column(
// children: [
// // Header Row with Resizable Columns
// Container(
// color: Colors.green,
// child: Row(
// children: List.generate(widget.titles.length, (index) {
// return Row(
// children: [
// Container(
// width: columnWidths[index],
// decoration: const BoxDecoration(
// color: Colors.green,
// ),
// child: Text(
// widget.titles[index],
// style: TextStyle(fontWeight: FontWeight.bold),
// textAlign: TextAlign.center,
// ),
// ),
// GestureDetector(
// onHorizontalDragUpdate: (details) {
// setState(() {
// columnWidths[index] = (columnWidths[index] +
// details.delta.dx)
// .clamp(50.0, 300.0); // Minimum & Maximum size
// });
// },
// child: MouseRegion(
// cursor: SystemMouseCursors
// .resizeColumn, // Set the cursor to resize
// child: Container(
// color: Colors.green,
// child: Container(
// color: Colors.black,
// width: 1,
// height: 50, // Height of the header cell
// ),
// ),
// ),
// ),
// ],
// );
// }),
// ),
// ),
// // Data Rows
// ...widget.rows.map((row) {
// return Row(
// children: List.generate(row.length, (index) {
// return Container(
// width: columnWidths[index],
// child: row[index],
// );
// }),
// );
// }).toList(),
// ],
// ),
// ),
// ),
// );
// }

View File

@ -0,0 +1,542 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:number_pagination/number_pagination.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/add_user_dialog.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/delete_user_dialog.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/edit_user_dialog.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/view/popup_menu_filter.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_state.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/creation_date_filter.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/de_activate_filter.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/name_filter.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/user_table.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class UsersPage extends StatelessWidget {
UsersPage({super.key});
@override
Widget build(BuildContext context) {
final TextEditingController searchController = TextEditingController();
Widget actionButton({required String title, required Function()? onTap}) {
return InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.only(left: 8, right: 8),
child: Text(
title,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: title == "Delete"
? ColorsManager.red
: ColorsManager.spaceColor,
fontWeight: FontWeight.w400,
),
),
),
);
}
Widget status({required String status}) {
return Center(
child: Container(
padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(20)),
color: status == "invited"
? ColorsManager.invitedOrange.withOpacity(0.5)
: status == "active"
? ColorsManager.activeGreen.withOpacity(0.5)
: ColorsManager.disabledPink.withOpacity(0.5),
),
child: Padding(
padding:
const EdgeInsets.only(left: 10, right: 10, bottom: 5, top: 5),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
status,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: status == "invited"
? ColorsManager.invitedOrangeText
: status == "active"
? ColorsManager.activeGreenText
: ColorsManager.disabledRedText,
fontWeight: FontWeight.w400,
),
),
],
),
),
),
);
}
Widget changeIconStatus(
{required String userId,
required String status,
required Function()? onTap}) {
return Center(
child: InkWell(
onTap: onTap,
child: Padding(
padding:
const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5),
child: SvgPicture.asset(
status == "invited"
? Assets.invitedIcon
: status == "active"
? Assets.activeUser
: Assets.deActiveUser,
height: 35,
),
),
),
);
}
return BlocBuilder<UserTableBloc, UserTableState>(
builder: (context, state) {
final screenSize = MediaQuery.of(context).size;
final _blocRole = BlocProvider.of<UserTableBloc>(context);
if (state is UsersLoadingState) {
_blocRole.add(ChangePage(_blocRole.currentPage));
return const Center(child: CircularProgressIndicator());
} else if (state is UsersLoadedState) {
return Padding(
padding: const EdgeInsets.all(20),
child: Align(
alignment: Alignment.topCenter,
child: ListView(
shrinkWrap: true,
children: [
Row(
children: [
Container(
decoration: containerDecoration.copyWith(
borderRadius: const BorderRadius.all(
Radius.circular(20),
),
),
width: screenSize.width * 0.4,
child: TextFormField(
controller: searchController,
onChanged: (value) {
context
.read<UserTableBloc>()
.add(SearchUsers(value));
},
style: const TextStyle(color: Colors.black),
decoration: textBoxDecoration(radios: 15)!.copyWith(
fillColor: ColorsManager.whiteColors,
errorStyle: const TextStyle(height: 0),
hintStyle: context.textTheme.titleSmall?.copyWith(
color: Colors.grey,
fontSize: 12,
),
hintText: 'Search',
suffixIcon: SizedBox(
child: SvgPicture.asset(
Assets.searchIconUser,
fit: BoxFit.none,
),
),
),
),
),
const SizedBox(width: 20),
InkWell(
onTap: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return const AddNewUserDialog();
},
).then((v) {
if (v != null) {
_blocRole.add(const GetUsers());
}
});
},
child: Container(
decoration: containerWhiteDecoration,
width: screenSize.width * 0.18,
height: 50,
child: const Center(
child: Text(
'Add New User',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: ColorsManager.blueColor,
),
),
),
),
),
],
),
const SizedBox(height: 25),
DynamicTableScreen(
onFilter: (columnIndex) {
_blocRole.add(FilterClearEvent());
if (columnIndex == 0) {
showNameMenu(
context: context,
isSelected: _blocRole.currentSortOrder,
aToZTap: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameAsc());
},
zToaTap: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameDesc());
},
);
}
if (columnIndex == 2) {
final Map<String, bool> checkboxStates = {
for (var item in _blocRole.jobTitle)
item: false, // Initialize with false
};
final RenderBox overlay = Overlay.of(context)
.context
.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 4,
240,
overlay.size.width / 4,
0,
),
list: _blocRole.jobTitle,
context: context,
checkboxStates: checkboxStates,
onOkPressed: () {
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
_blocRole.add(FilterUsersByJobEvent(selectedItems));
},
onSortAtoZ: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameAsc());
},
onSortZtoA: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameDesc());
},
);
}
if (columnIndex == 3) {
final Map<String, bool> checkboxStates = {
for (var item in _blocRole.roleTypes)
item: _blocRole.selectedRoles.contains(item),
};
final RenderBox overlay = Overlay.of(context)
.context
.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 4,
240,
overlay.size.width / 4,
0,
),
list: _blocRole.roleTypes,
context: context,
checkboxStates: checkboxStates,
onOkPressed: () {
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
context
.read<UserTableBloc>()
.add(FilterUsersByRoleEvent(selectedItems));
},
onSortAtoZ: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameAsc());
},
onSortZtoA: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameDesc());
},
);
}
if (columnIndex == 4) {
showDateFilterMenu(
context: context,
isSelected: _blocRole.currentSortOrderDate,
aToZTap: () {
context
.read<UserTableBloc>()
.add(const DateNewestToOldestEvent());
},
zToaTap: () {
context
.read<UserTableBloc>()
.add(const DateOldestToNewestEvent());
},
);
}
if (columnIndex == 6) {
final Map<String, bool> checkboxStates = {
for (var item in _blocRole.createdBy)
item: _blocRole.selectedCreatedBy.contains(item),
};
final RenderBox overlay = Overlay.of(context)
.context
.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 1,
240,
overlay.size.width / 4,
0,
),
list: _blocRole.createdBy,
context: context,
checkboxStates: checkboxStates,
onOkPressed: () {
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
_blocRole
.add(FilterUsersByCreatedEvent(selectedItems));
},
onSortAtoZ: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameAsc());
},
onSortZtoA: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameDesc());
},
);
}
if (columnIndex == 7) {
final Map<String, bool> checkboxStates = {
for (var item in _blocRole.status)
item: _blocRole.selectedCreatedBy.contains(item),
};
final RenderBox overlay = Overlay.of(context)
.context
.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 0,
240,
overlay.size.width / 4,
0,
),
list: _blocRole.status,
context: context,
checkboxStates: checkboxStates,
onOkPressed: () {
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
_blocRole
.add(FilterUsersByCreatedEvent(selectedItems));
},
onSortAtoZ: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameAsc());
},
onSortZtoA: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameDesc());
},
);
}
if (columnIndex == 8) {
showDeActivateFilterMenu(
context: context,
isSelected: _blocRole.currentSortOrderDate,
aToZTap: () {
context
.read<UserTableBloc>()
.add(const DateNewestToOldestEvent());
},
zToaTap: () {
context
.read<UserTableBloc>()
.add(const DateOldestToNewestEvent());
},
);
}
},
titles: const [
"Full Name",
"Email Address",
"Job Title",
"Role",
"Creation Date",
"Creation Time",
"Created By",
"Status",
"De/Activate",
"Action"
],
rows: state.users.map((user) {
return [
Text('${user.firstName} ${user.lastName}'),
Text(user.email ),
Text(user.jobTitle ?? ''),
Text(user.roleType ?? ''),
Text(user.createdDate ?? ''),
Text(user.createdTime ?? ''),
Text(user.invitedBy),
status(
status: user.isEnabled == false
? 'disabled'
: user.status,
),
changeIconStatus(
status: user.isEnabled == false
? 'disabled'
: user.status,
userId: user.uuid,
onTap: user.status != "invited"
? () {
// final newStatus = user.status == 'active'
// ? 'disabled'
// : user.status == 'disabled'
// ? 'invited'
// : 'active';
context.read<UserTableBloc>().add(
ChangeUserStatus(
userId: user.uuid,
newStatus: user.isEnabled == false
? 'disabled'
: user.status));
}
: null,
),
Row(
children: [
// actionButton(
// title: "Activity Log",
// onTap: () {},
// ),
actionButton(
title: "Edit",
onTap: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return EditUserDialog(userId: user.uuid);
},
).then((v) {
if (v != null) {
if (v != null) {
_blocRole.add(const GetUsers());
}
}
});
},
),
actionButton(
title: "Delete",
onTap: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return DeleteUserDialog(
onTapDelete: () {
_blocRole.add(DeleteUserEvent(
user.uuid, context));
},
);
},
).then((v) {
if (v != null) {
if (v != null) {
_blocRole.add(const GetUsers());
}
}
});
},
),
],
),
];
}).toList(),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SizedBox(
width: 500,
child: NumberPagination(
buttonRadius: 10,
selectedButtonColor: ColorsManager.secondaryColor,
buttonUnSelectedBorderColor:
ColorsManager.grayBorder,
lastPageIcon:
const Icon(Icons.keyboard_double_arrow_right),
firstPageIcon:
const Icon(Icons.keyboard_double_arrow_left),
totalPages: (_blocRole.users.length /
_blocRole.itemsPerPage)
.ceil(),
currentPage: _blocRole.currentPage,
onPageChanged: (int pageNumber) {
_blocRole.currentPage = pageNumber;
context
.read<UserTableBloc>()
.add(ChangePage(pageNumber));
},
),
),
],
),
),
],
),
),
);
} else if (state is ErrorState) {
return Center(child: Text(state.message));
} else {
return const Center(child: Text('No data available.'));
}
},
);
}
}

View File

@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class RoleCard extends StatelessWidget {
final String name;
const RoleCard({super.key, required this.name});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: ColorsManager.whiteColors, // Card background color
borderRadius: BorderRadius.circular(20), // Rounded corners
boxShadow: [
BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.2), // Shadow color
blurRadius: 20, // Spread of the shadow
offset: const Offset(2, 2), // No directional bias
spreadRadius: 1, // Ensures the shadow is more noticeable
),
],
),
child: Container(
decoration: BoxDecoration(
color: ColorsManager.whiteColors,
borderRadius: BorderRadius.circular(20),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const CircleAvatar(
backgroundColor: ColorsManager.neutralGray,
radius: 65,
child: CircleAvatar(
backgroundColor: ColorsManager.circleRolesBackground,
radius: 62,
child: Icon(
Icons.admin_panel_settings,
size: 40,
color: Colors.blue,
),
),
),
Text(
name,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,94 @@
import 'package:flutter/material.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/roles_and_permission/bloc/roles_permission_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_state.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/bloc/user_table_event.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/users_table/view/users_page.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
class RolesAndPermissionPage extends StatelessWidget {
const RolesAndPermissionPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => RolesPermissionBloc(),
child: BlocConsumer<RolesPermissionBloc, RolesPermissionState>(
listener: (context, state) {},
builder: (context, state) {
final _blocRole = BlocProvider.of<RolesPermissionBloc>(context);
return state is RolesLoadingState
? const Center(child: CircularProgressIndicator())
: WebScaffold(
enableMenuSidebar: false,
appBarTitle: FittedBox(
child: Text(
'Roles & Permissions',
style: Theme.of(context).textTheme.headlineLarge,
),
),
rightBody: const NavigateHomeGridView(),
centerBody: Row(
children: [
// TextButton(
// style: TextButton.styleFrom(
// backgroundColor: null,
// ),
// onPressed: () {
// _blocRole.add(const ChangeTapSelected(true));
// },
// child: Text(
// 'Roles',
// style: context.textTheme.titleMedium?.copyWith(
// color: (_blocRole.tapSelect == true)
// ? ColorsManager.whiteColors
// : ColorsManager.grayColor,
// fontWeight: (_blocRole.tapSelect == true)
// ? FontWeight.w700
// : FontWeight.w400,
// ),
// ),
// ),
TextButton(
style: TextButton.styleFrom(
backgroundColor: null,
),
onPressed: () {
// _blocRole.add(const ChangeTapSelected(false));
},
child: Text(
'Users',
style: context.textTheme.titleMedium?.copyWith(
color: (_blocRole.tapSelect == true)
? ColorsManager.whiteColors
: ColorsManager.grayColor,
fontWeight: (_blocRole.tapSelect == true)
? FontWeight.w700
: FontWeight.w400,
),
),
),
],
),
scaffoldBody: BlocProvider<UserTableBloc>(
create: (context) => UserTableBloc()..add(const GetUsers()),
child: UsersPage(),
)
// _blocRole.tapSelect == false
// ? UsersPage(
// blocRole: _blocRole,
// )
// : RolesPage(
// blocRole: _blocRole,
// )
);
},
),
);
}
}

View File

@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/roles_and_permission/bloc/roles_permission_bloc.dart';
import 'package:syncrow_web/pages/roles_and_permission/view/role_card.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/style.dart';
class RolesPage extends StatelessWidget {
final RolesPermissionBloc blocRole;
const RolesPage({super.key, required this.blocRole});
@override
Widget build(BuildContext context) {
final TextEditingController searchController = TextEditingController();
double screenWidth = MediaQuery.of(context).size.width;
int crossAxisCount = (screenWidth ~/ 200).clamp(1, 6);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: containerDecoration.copyWith(
borderRadius: const BorderRadius.all(
Radius.circular(20),
),
),
width: 250,
child: TextFormField(
controller: searchController,
style: const TextStyle(color: Colors.black),
decoration: textBoxDecoration(radios: 15)!.copyWith(
fillColor: ColorsManager.whiteColors,
errorStyle: const TextStyle(height: 0),
hintStyle: context.textTheme.titleSmall?.copyWith(
color: Colors.grey,
fontSize: 12,
),
hintText: 'Search',
suffixIcon: SvgPicture.asset(Assets.searchIconUser)),
),
),
Expanded(
child: GridView.builder(
padding: const EdgeInsets.all(10),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 2 / 2.5,
),
itemCount: blocRole.roleModel.length ?? 0,
itemBuilder: (context, index) {
final role = blocRole.roleModel[index];
if (role == null) {
return const SizedBox.shrink();
}
return RoleCard(
name: role.roleName ?? 'Unknown',
);
},
),
),
],
);
}
}

View File

@ -83,7 +83,7 @@ class DraggableCard extends StatelessWidget {
height: 50,
width: 50,
decoration: BoxDecoration(
color: ColorsManager.CircleImageBackground,
color: ColorsManager.circleImageBackground,
borderRadius: BorderRadius.circular(90),
border: Border.all(
color: ColorsManager.graysColor,

View File

@ -260,6 +260,7 @@ class CommunitySpaceManagementApi {
.replaceAll('{communityId}', communityId)
.replaceAll('{projectId}', TempConst.projectId),
expectedResponseModel: (json) {
print(json);
final spaceModels = (json['data'] as List)
.map((spaceJson) => SpaceModel.fromJson(spaceJson))
.toList();

View File

@ -0,0 +1,224 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/rendering.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/edit_user_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/role_type_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/model/roles_user_model.dart';
import 'package:syncrow_web/pages/roles_and_permission/users_page/add_user_dialog/model/permission_option_model.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart';
class UserPermissionApi {
static final HTTPService _httpService = HTTPService();
Future<List<RolesUserModel>> fetchUsers() async {
try {
final response = await _httpService.get(
path: ApiEndpoints.getUsers,
showServerMessage: true,
expectedResponseModel: (json) {
debugPrint('fetchUsers Response: $json');
final List<dynamic> data =
json['data'] ?? []; // Default to an empty list if no data
return data.map((item) => RolesUserModel.fromJson(item)).toList();
},
);
return response;
} catch (e, stackTrace) {
debugPrint('Error in fetchUsers: $e');
rethrow;
}
}
fetchRoles() async {
final response = await _httpService.get(
path: ApiEndpoints.roleTypes,
showServerMessage: true,
expectedResponseModel: (json) {
final List<RoleTypeModel> fetchedRoles = (json['data'] as List)
.map((item) => RoleTypeModel.fromJson(item))
.toList();
return fetchedRoles;
},
);
return response;
}
Future<List<PermissionOption>> fetchPermission(roleUuid) async {
final response = await _httpService.get(
path: ApiEndpoints.permission.replaceAll("roleUuid", roleUuid),
showServerMessage: true,
expectedResponseModel: (json) {
return (json as List)
.map((data) => PermissionOption.fromJson(data))
.toList();
},
);
return response ?? [];
}
Future<bool> sendInviteUser({
String? firstName,
String? lastName,
String? email,
String? jobTitle,
String? phoneNumber,
String? roleUuid,
List<String>? spaceUuids,
}) async {
try {
final body = <String, dynamic>{
"firstName": firstName,
"lastName": lastName,
"email": email,
"jobTitle": jobTitle != '' ? jobTitle : " ",
"phoneNumber": phoneNumber != '' ? phoneNumber : " ",
"roleUuid": roleUuid,
"projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c",
"spaceUuids": spaceUuids,
};
final response = await _httpService.post(
path: ApiEndpoints.inviteUser,
showServerMessage: true,
body: jsonEncode(body),
expectedResponseModel: (json) {
if (json['statusCode'] != 400) {
return json["success"];
} else {
return false;
}
},
);
print('sendInviteUser=$body');
return response ?? [];
} on DioException catch (e) {
return false;
} catch (e) {
return false;
}
}
Future<String?> checkEmail(String email) async {
try {
final response = await _httpService.post(
path: ApiEndpoints.checkEmail,
showServerMessage: true,
body: {"email": email},
expectedResponseModel: (json) {
if (json['statusCode'] != 400) {
var message = json["message"];
if (message is String) {
return message;
} else {
return 'Unexpected message format';
}
}
return null;
},
);
return response ?? 'Unknown error occurred';
} on DioException catch (e) {
if (e.response != null) {
final errorMessage = e.response?.data['error'];
return errorMessage is String
? errorMessage
: 'Error occurred while checking email';
}
return 'Error occurred while checking email';
} catch (e) {
return e.toString();
}
}
Future<EditUserModel?> fetchUserById(userUuid) async {
final response = await _httpService.get(
path: ApiEndpoints.getUserById.replaceAll("{userUuid}", userUuid),
showServerMessage: true,
expectedResponseModel: (json) {
EditUserModel res = EditUserModel.fromJson(json['data']);
return res;
},
);
return response;
}
Future<bool> editInviteUser({
String? firstName,
String? userId,
String? lastName,
String? jobTitle,
String? phoneNumber,
String? roleUuid,
List<String>? spaceUuids,
}) async {
try {
final body = <String, dynamic>{
"firstName": firstName,
"lastName": lastName,
"jobTitle": jobTitle != '' ? jobTitle : " ",
"phoneNumber": phoneNumber != '' ? phoneNumber : " ",
"roleUuid": roleUuid,
"projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c",
"spaceUuids": spaceUuids,
};
final response = await _httpService.put(
path: ApiEndpoints.editUser.replaceAll('{inviteUserUuid}', userId!),
body: jsonEncode(body),
expectedResponseModel: (json) {
if (json['statusCode'] != 400) {
return json["success"];
} else {
return false;
}
},
);
return response;
} on DioException catch (e) {
return false;
} catch (e) {
return false;
}
}
Future<bool> deleteUserById(userUuid) async {
try {
final response = await _httpService.delete(
path: ApiEndpoints.deleteUser.replaceAll("{inviteUserUuid}", userUuid),
showServerMessage: true,
expectedResponseModel: (json) {
return json['success'];
},
);
return response;
} catch (e) {
return false;
}
}
Future<bool> changeUserStatusById(userUuid, status) async {
try {
Map<String, dynamic> bodya = {
"disable": status,
"projectUuid": "0e62577c-06fa-41b9-8a92-99a21fbaf51c"
};
print('changeUserStatusById==$bodya');
print('changeUserStatusById==$userUuid');
final response = await _httpService.put(
path: ApiEndpoints.changeUserStatus
.replaceAll("{invitedUserUuid}", userUuid),
body: bodya,
expectedResponseModel: (json) {
print('changeUserStatusById==${json['success']}');
return json['success'];
},
);
return response;
} catch (e) {
return false;
print(e);
}
}
}

View File

@ -3,6 +3,7 @@ import 'package:syncrow_web/pages/access_management/view/access_management.dart'
import 'package:syncrow_web/pages/auth/view/login_page.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/view/device_managment_page.dart';
import 'package:syncrow_web/pages/home/view/home_page.dart';
import 'package:syncrow_web/pages/roles_and_permission/view/roles_and_permission_page.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/view/spaces_management_page.dart';
import 'package:syncrow_web/pages/visitor_password/view/visitor_password_dialog.dart';
import 'package:syncrow_web/utils/constants/routes_const.dart';
@ -33,6 +34,9 @@ class AppRoutes {
GoRoute(
path: RoutesConst.spacesManagementPage,
builder: (context, state) => const SpaceManagementPage()),
GoRoute(
path: RoutesConst.rolesAndPermissions,
builder: (context, state) => const RolesAndPermissionPage()),
];
}
}

View File

@ -60,5 +60,14 @@ abstract class ColorsManager {
static const Color softGray = Color(0xFFD5D5D5);
static const Color semiTransparentBlack = Color(0x19000000);
static const Color dataHeaderGrey = Color(0x33999999);
static const Color circleImageBackground = Color(0xFFF4F4F4);
static const Color circleRolesBackground = Color(0xFFF8F8F8);
static const Color activeGreen = Color(0xFF99FF93);
static const Color activeGreenText = Color(0xFF008905);
static const Color disabledPink = Color(0xFFFF9395);
static const Color disabledRedText = Color(0xFF890002);
static const Color invitedOrange = Color(0xFFFFE193);
static const Color invitedOrangeText = Color(0xFFFFBF00);
//background: #F8F8F8;
}
//background: #background: #5D5D5D;

View File

@ -1,6 +1,7 @@
import 'package:flutter_dotenv/flutter_dotenv.dart';
abstract class ApiEndpoints {
static const String projectUuid = "0e62577c-06fa-41b9-8a92-99a21fbaf51c";
static String baseUrl = dotenv.env['BASE_URL'] ?? '';
static const String signUp = '/authentication/user/signup';
static const String login = '/authentication/user/login';
@ -101,4 +102,18 @@ abstract class ApiEndpoints {
static const String listSpaceModels = '/projects/{projectId}/space-models';
static const String createSpaceModel = '/projects/{projectId}/space-models';
static const String getSpaceModel = '/projects/{projectId}/space-models/{spaceModelUuid}';
static const String roleTypes = '/role/types';
static const String permission = '/permission/{roleUuid}';
static const String inviteUser = '/invite-user';
static const String checkEmail = '/invite-user/check-email';
static const String getUsers = '/projects/${projectUuid}/user';
static const String getUserById = '/projects/${projectUuid}/user/{userUuid}';
static const String editUser = '/invite-user/{inviteUserUuid}';
static const String deleteUser = '/invite-user/{inviteUserUuid}';
static const String changeUserStatus =
'/invite-user/{invitedUserUuid}/disable';
// static const String updateAutomation = '/automation/{automationId}';
}

View File

@ -13,10 +13,12 @@ class Assets {
static const String rightLine = "assets/images/right_line.png";
static const String google = "assets/images/google.svg";
static const String facebook = "assets/images/facebook.svg";
static const String invisiblePassword = "assets/images/Password_invisible.svg";
static const String invisiblePassword =
"assets/images/Password_invisible.svg";
static const String visiblePassword = "assets/images/password_visible.svg";
static const String accessIcon = "assets/images/access_icon.svg";
static const String spaseManagementIcon = "assets/images/spase_management_icon.svg";
static const String spaseManagementIcon =
"assets/images/spase_management_icon.svg";
static const String devicesIcon = "assets/images/devices_icon.svg";
static const String moveinIcon = "assets/images/movein_icon.svg";
static const String constructionIcon = "assets/images/construction_icon.svg";
@ -29,13 +31,15 @@ class Assets {
static const String emptyTable = "assets/images/empty_table.svg";
// General assets
static const String motionlessDetection = "assets/icons/motionless_detection.svg";
static const String motionlessDetection =
"assets/icons/motionless_detection.svg";
static const String acHeating = "assets/icons/ac_heating.svg";
static const String acPowerOff = "assets/icons/ac_power_off.svg";
static const String acFanMiddle = "assets/icons/ac_fan_middle.svg";
static const String switchAlarmSound = "assets/icons/switch_alarm_sound.svg";
static const String resetOff = "assets/icons/reset_off.svg";
static const String sensitivityOperationIcon = "assets/icons/sesitivity_operation_icon.svg";
static const String sensitivityOperationIcon =
"assets/icons/sesitivity_operation_icon.svg";
static const String motionDetection = "assets/icons/motion_detection.svg";
static const String freezing = "assets/icons/freezing.svg";
static const String indicator = "assets/icons/indicator.svg";
@ -56,7 +60,8 @@ class Assets {
static const String celsiusDegrees = "assets/icons/celsius_degrees.svg";
static const String masterState = "assets/icons/master_state.svg";
static const String acPower = "assets/icons/ac_power.svg";
static const String farDetectionFunction = "assets/icons/far_detection_function.svg";
static const String farDetectionFunction =
"assets/icons/far_detection_function.svg";
static const String nobodyTime = "assets/icons/nobody_time.svg";
// Automation functions
@ -64,33 +69,47 @@ class Assets {
"assets/icons/automation_functions/temp_password_unlock.svg";
static const String doorlockNormalOpen =
"assets/icons/automation_functions/doorlock_normal_open.svg";
static const String doorbell = "assets/icons/automation_functions/doorbell.svg";
static const String doorbell =
"assets/icons/automation_functions/doorbell.svg";
static const String remoteUnlockViaApp =
"assets/icons/automation_functions/remote_unlock_via_app.svg";
static const String doubleLock = "assets/icons/automation_functions/double_lock.svg";
static const String selfTestResult = "assets/icons/automation_functions/self_test_result.svg";
static const String lockAlarm = "assets/icons/automation_functions/lock_alarm.svg";
static const String presenceState = "assets/icons/automation_functions/presence_state.svg";
static const String currentTemp = "assets/icons/automation_functions/current_temp.svg";
static const String presence = "assets/icons/automation_functions/presence.svg";
static const String doubleLock =
"assets/icons/automation_functions/double_lock.svg";
static const String selfTestResult =
"assets/icons/automation_functions/self_test_result.svg";
static const String lockAlarm =
"assets/icons/automation_functions/lock_alarm.svg";
static const String presenceState =
"assets/icons/automation_functions/presence_state.svg";
static const String currentTemp =
"assets/icons/automation_functions/current_temp.svg";
static const String presence =
"assets/icons/automation_functions/presence.svg";
static const String residualElectricity =
"assets/icons/automation_functions/residual_electricity.svg";
static const String hijackAlarm = "assets/icons/automation_functions/hijack_alarm.svg";
static const String passwordUnlock = "assets/icons/automation_functions/password_unlock.svg";
static const String hijackAlarm =
"assets/icons/automation_functions/hijack_alarm.svg";
static const String passwordUnlock =
"assets/icons/automation_functions/password_unlock.svg";
static const String remoteUnlockRequest =
"assets/icons/automation_functions/remote_unlock_req.svg";
static const String cardUnlock = "assets/icons/automation_functions/card_unlock.svg";
static const String cardUnlock =
"assets/icons/automation_functions/card_unlock.svg";
static const String motion = "assets/icons/automation_functions/motion.svg";
static const String fingerprintUnlock =
"assets/icons/automation_functions/fingerprint_unlock.svg";
// Presence Sensor Assets
static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg";
static const String sensorPresenceIcon = "assets/icons/sensor_presence_ic.svg";
static const String sensorPresenceIcon =
"assets/icons/sensor_presence_ic.svg";
static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg";
static const String illuminanceRecordIcon = "assets/icons/illuminance_record_ic.svg";
static const String presenceRecordIcon = "assets/icons/presence_record_ic.svg";
static const String helpDescriptionIcon = "assets/icons/help_description_ic.svg";
static const String illuminanceRecordIcon =
"assets/icons/illuminance_record_ic.svg";
static const String presenceRecordIcon =
"assets/icons/presence_record_ic.svg";
static const String helpDescriptionIcon =
"assets/icons/help_description_ic.svg";
static const String lightPulp = "assets/icons/light_pulb.svg";
static const String acDevice = "assets/icons/ac_device.svg";
@ -140,10 +159,12 @@ class Assets {
static const String unit = 'assets/icons/unit_icon.svg';
static const String villa = 'assets/icons/villa_icon.svg';
static const String iconEdit = 'assets/icons/icon_edit_icon.svg';
static const String textFieldSearch = 'assets/icons/textfield_search_icon.svg';
static const String textFieldSearch =
'assets/icons/textfield_search_icon.svg';
static const String roundedAddIcon = 'assets/icons/rounded_add_icon.svg';
static const String addIcon = 'assets/icons/add_icon.svg';
static const String smartThermostatIcon = 'assets/icons/smart_thermostat_icon.svg';
static const String smartThermostatIcon =
'assets/icons/smart_thermostat_icon.svg';
static const String smartLightIcon = 'assets/icons/smart_light_icon.svg';
static const String presenceSensor = 'assets/icons/presence_sensor.svg';
static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg';
@ -191,7 +212,8 @@ class Assets {
//assets/icons/water_leak_normal.svg
static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg';
//assets/icons/water_leak_detected.svg
static const String waterLeakDetected = 'assets/icons/water_leak_detected.svg';
static const String waterLeakDetected =
'assets/icons/water_leak_detected.svg';
//assets/icons/automation_records.svg
static const String automationRecords = 'assets/icons/automation_records.svg';
@ -256,40 +278,64 @@ class Assets {
static const String delay = 'assets/icons/routine/delay.svg';
// Assets for functions_icons
static const String assetsSensitivityFunction = "assets/icons/functions_icons/sensitivity.svg";
static const String assetsSensitivityFunction =
"assets/icons/functions_icons/sensitivity.svg";
static const String assetsSensitivityOperationIcon =
"assets/icons/functions_icons/sesitivity_operation_icon.svg";
static const String assetsAcPower = "assets/icons/functions_icons/ac_power.svg";
static const String assetsAcPowerOFF = "assets/icons/functions_icons/ac_power_off.svg";
static const String assetsChildLock = "assets/icons/functions_icons/child_lock.svg";
static const String assetsFreezing = "assets/icons/functions_icons/freezing.svg";
static const String assetsFanSpeed = "assets/icons/functions_icons/fan_speed.svg";
static const String assetsAcCooling = "assets/icons/functions_icons/ac_cooling.svg";
static const String assetsAcHeating = "assets/icons/functions_icons/ac_heating.svg";
static const String assetsCelsiusDegrees = "assets/icons/functions_icons/celsius_degrees.svg";
static const String assetsTempreture = "assets/icons/functions_icons/tempreture.svg";
static const String assetsAcFanLow = "assets/icons/functions_icons/ac_fan_low.svg";
static const String assetsAcFanMiddle = "assets/icons/functions_icons/ac_fan_middle.svg";
static const String assetsAcFanHigh = "assets/icons/functions_icons/ac_fan_high.svg";
static const String assetsAcFanAuto = "assets/icons/functions_icons/ac_fan_auto.svg";
static const String assetsSceneChildLock = "assets/icons/functions_icons/scene_child_lock.svg";
static const String assetsAcPower =
"assets/icons/functions_icons/ac_power.svg";
static const String assetsAcPowerOFF =
"assets/icons/functions_icons/ac_power_off.svg";
static const String assetsChildLock =
"assets/icons/functions_icons/child_lock.svg";
static const String assetsFreezing =
"assets/icons/functions_icons/freezing.svg";
static const String assetsFanSpeed =
"assets/icons/functions_icons/fan_speed.svg";
static const String assetsAcCooling =
"assets/icons/functions_icons/ac_cooling.svg";
static const String assetsAcHeating =
"assets/icons/functions_icons/ac_heating.svg";
static const String assetsCelsiusDegrees =
"assets/icons/functions_icons/celsius_degrees.svg";
static const String assetsTempreture =
"assets/icons/functions_icons/tempreture.svg";
static const String assetsAcFanLow =
"assets/icons/functions_icons/ac_fan_low.svg";
static const String assetsAcFanMiddle =
"assets/icons/functions_icons/ac_fan_middle.svg";
static const String assetsAcFanHigh =
"assets/icons/functions_icons/ac_fan_high.svg";
static const String assetsAcFanAuto =
"assets/icons/functions_icons/ac_fan_auto.svg";
static const String assetsSceneChildLock =
"assets/icons/functions_icons/scene_child_lock.svg";
static const String assetsSceneChildUnlock =
"assets/icons/functions_icons/scene_child_unlock.svg";
static const String assetsSceneRefresh = "assets/icons/functions_icons/scene_refresh.svg";
static const String assetsLightCountdown = "assets/icons/functions_icons/light_countdown.svg";
static const String assetsFarDetection = "assets/icons/functions_icons/far_detection.svg";
static const String assetsSceneRefresh =
"assets/icons/functions_icons/scene_refresh.svg";
static const String assetsLightCountdown =
"assets/icons/functions_icons/light_countdown.svg";
static const String assetsFarDetection =
"assets/icons/functions_icons/far_detection.svg";
static const String assetsFarDetectionFunction =
"assets/icons/functions_icons/far_detection_function.svg";
static const String assetsIndicator = "assets/icons/functions_icons/indicator.svg";
static const String assetsMotionDetection = "assets/icons/functions_icons/motion_detection.svg";
static const String assetsIndicator =
"assets/icons/functions_icons/indicator.svg";
static const String assetsMotionDetection =
"assets/icons/functions_icons/motion_detection.svg";
static const String assetsMotionlessDetection =
"assets/icons/functions_icons/motionless_detection.svg";
static const String assetsNobodyTime = "assets/icons/functions_icons/nobody_time.svg";
static const String assetsFactoryReset = "assets/icons/functions_icons/factory_reset.svg";
static const String assetsMasterState = "assets/icons/functions_icons/master_state.svg";
static const String assetsNobodyTime =
"assets/icons/functions_icons/nobody_time.svg";
static const String assetsFactoryReset =
"assets/icons/functions_icons/factory_reset.svg";
static const String assetsMasterState =
"assets/icons/functions_icons/master_state.svg";
static const String assetsSwitchAlarmSound =
"assets/icons/functions_icons/switch_alarm_sound.svg";
static const String assetsResetOff = "assets/icons/functions_icons/reset_off.svg";
static const String assetsResetOff =
"assets/icons/functions_icons/reset_off.svg";
// Assets for automation_functions
static const String assetsCardUnlock =
@ -320,11 +366,36 @@ class Assets {
"assets/icons/functions_icons/automation_functions/self_test_result.svg";
static const String assetsPresence =
"assets/icons/functions_icons/automation_functions/presence.svg";
static const String assetsMotion = "assets/icons/functions_icons/automation_functions/motion.svg";
static const String assetsMotion =
"assets/icons/functions_icons/automation_functions/motion.svg";
static const String assetsCurrentTemp =
"assets/icons/functions_icons/automation_functions/current_temp.svg";
static const String assetsPresenceState =
"assets/icons/functions_icons/automation_functions/presence_state.svg";
//assets/icons/routine/automation.svg
static const String automation = 'assets/icons/routine/automation.svg';
static const String searchIconUser = 'assets/icons/search_icon_user.svg';
static const String searchIcoUser = 'assets/icons/search_icon_user.svg';
static const String activeUser = 'assets/icons/active_user.svg';
static const String deActiveUser = 'assets/icons/deactive_user.svg';
static const String invitedIcon = 'assets/icons/invited_icon.svg';
static const String rectangleCheckBox =
'assets/icons/rectangle_check_box.png';
static const String CheckBoxChecked = 'assets/icons/box_checked.png';
static const String emptyBox = 'assets/icons/empty_box.png';
static const String completeProcessIcon =
'assets/icons/compleate_process_icon.svg';
static const String currentProcessIcon =
'assets/icons/current_process_icon.svg';
static const String uncomplete_ProcessIcon =
'assets/icons/uncompleate_process_icon.svg';
static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg';
static const String arrowForward = 'assets/icons/arrow_forward.svg';
static const String arrowDown = 'assets/icons/arrow_down.svg';
static const String userManagement = 'assets/icons/user_management.svg';
static const String filterTableIcon = 'assets/icons/filter_table_icon.svg';
static const String ZtoAIcon = 'assets/icons/ztoa_icon.png';
static const String AtoZIcon = 'assets/icons/atoz_icon.png';
}
//user_management.svg

View File

@ -5,4 +5,5 @@ class RoutesConst {
static const String accessManagementPage = '/access-management-page';
static const String deviceManagementPage = '/device-management-page';
static const String spacesManagementPage = '/spaces_management-page';
static const String rolesAndPermissions = '/roles_and_Permissions-page';
}

View File

@ -2,51 +2,59 @@ import 'package:flutter/material.dart';
import 'color_manager.dart';
InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration(
InputDecoration? textBoxDecoration(
{bool suffixIcon = false, double radios = 8}) =>
InputDecoration(
focusColor: ColorsManager.grayColor,
suffixIcon: suffixIcon ? const Icon(Icons.search) : null,
hintText: 'Search',
filled: true, // Enable background filling
fillColor: const Color(0xffF5F6F7), // Set the background color
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), // Add border radius
borderRadius: BorderRadius.circular(radios), // Add border radius
borderSide: BorderSide.none, // Remove the underline
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), // Add border radius
borderRadius: BorderRadius.circular(radios), // Add border radius
borderSide: BorderSide.none, // Remove the underline
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), // Add border radius
borderRadius: BorderRadius.circular(radios), // Add border radius
borderSide: BorderSide.none, // Remove the underline
),
errorBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.red, width: 2),
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(radios),
),
focusedErrorBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.red, width: 2),
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(radios),
),
);
BoxDecoration containerDecoration = BoxDecoration(boxShadow: [
BoxDecoration containerDecoration = BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
spreadRadius: 2,
blurRadius: 4,
offset: const Offset(0, 5), // changes position of shadow
),
], color: ColorsManager.boxColor, borderRadius: const BorderRadius.all(Radius.circular(10)));
],
color: ColorsManager.boxColor,
borderRadius: const BorderRadius.all(Radius.circular(10)));
BoxDecoration containerWhiteDecoration = BoxDecoration(boxShadow: [
BoxDecoration containerWhiteDecoration = BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.3),
spreadRadius: 2,
blurRadius: 4,
offset: const Offset(0, 5), // changes position of shadow
),
], color: ColorsManager.whiteColors, borderRadius: const BorderRadius.all(Radius.circular(15)));
],
color: ColorsManager.whiteColors,
borderRadius: const BorderRadius.all(Radius.circular(15)));
BoxDecoration subSectionContainerDecoration = BoxDecoration(
color: ColorsManager.whiteColors,
@ -59,3 +67,30 @@ BoxDecoration subSectionContainerDecoration = BoxDecoration(
),
],
);
InputDecoration inputTextFormDeco({hintText}) => InputDecoration(
hintText: hintText,
border: const OutlineInputBorder(
borderSide: BorderSide(
width: 1,
color: ColorsManager.textGray, // Border color for unfocused state
),
borderRadius: BorderRadius.all(Radius.circular(10)),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(
width: 2,
color: ColorsManager.textGray, // Border color when focused
),
borderRadius: BorderRadius.all(Radius.circular(10)),
),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(
width: 1,
color: ColorsManager
.textGray // Border color for enabled (but unfocused) state
),
borderRadius: BorderRadius.all(Radius.circular(10)),
),
);

View File

@ -53,7 +53,8 @@ class _UserDropdownMenuState extends State<UserDropdownMenu> {
}
Future<void> _showPopupMenu(BuildContext context) async {
final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox;
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB(
overlay.size.width,
@ -85,29 +86,31 @@ class _UserDropdownMenuState extends State<UserDropdownMenu> {
// ),
// ),
// ),
// PopupMenuItem(
// onTap: () {},
// child: ListTile(
// leading: SvgPicture.asset(Assets.settings),
// title: Text(
// "Settings",
// style: context.textTheme.bodyMedium,
// ),
// ),
// ),
PopupMenuItem(
onTap: () {
context.go(RoutesConst.rolesAndPermissions);
},
child: ListTile(
leading: SvgPicture.asset(Assets.userManagement),
title: Text(
"User Management",
style: context.textTheme.bodyMedium,
),
),
),
PopupMenuItem(
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) {
final size = MediaQuery.of(context).size;
return AlertDialog(
alignment: Alignment.center,
content: SizedBox(
height: 200,
width: 400,
child: Padding(
padding: const EdgeInsets.only(top: 24, left: 24, right: 24),
padding:
const EdgeInsets.only(top: 24, left: 24, right: 24),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
@ -120,7 +123,10 @@ class _UserDropdownMenuState extends State<UserDropdownMenu> {
padding: const EdgeInsets.only(top: 16),
child: Text(
'Log out of your Syncrow account',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Colors.black,
@ -151,11 +157,15 @@ class _UserDropdownMenuState extends State<UserDropdownMenu> {
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'${widget.user?.firstName ?? ''} ${widget.user?.lastName}',
style: Theme.of(context).textTheme.titleMedium!.copyWith(
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 20,
@ -163,7 +173,10 @@ class _UserDropdownMenuState extends State<UserDropdownMenu> {
),
Text(
' ${widget.user?.email}',
style: Theme.of(context).textTheme.bodySmall!.copyWith(
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: Colors.black,
),
),
@ -189,7 +202,10 @@ class _UserDropdownMenuState extends State<UserDropdownMenu> {
elevation: 1,
child: Text(
'Cancel',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
fontSize: 12,
color: Colors.black,
),

36
macos/Podfile.lock Normal file
View File

@ -0,0 +1,36 @@
PODS:
- flutter_secure_storage_macos (6.1.1):
- FlutterMacOS
- FlutterMacOS (1.0.0)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
EXTERNAL SOURCES:
flutter_secure_storage_macos:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos
FlutterMacOS:
:path: Flutter/ephemeral
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
SPEC CHECKSUMS:
flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
COCOAPODS: 1.16.2

View File

@ -21,6 +21,8 @@
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
108157F896CD9F637B06D7C0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DAF1C60594A51D692304366 /* Pods_Runner.framework */; };
2D0F1F294F673EF0DB5E4CA1 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */; };
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
@ -60,11 +62,12 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
24D7BEF98D33245EFB9F6A1B /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* syncrow_web.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "syncrow_web.app"; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10ED2044A3C60003C045 /* syncrow_web.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = syncrow_web.app; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
@ -76,8 +79,15 @@
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
5DAF1C60594A51D692304366 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
81F2F315AC5109F6F5D27BE6 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
96C46007EE0A4E9E1D6D74CE /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
A604E311B663FBF4B7C54DC5 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
AB949539E0D0A8E2BDAB9ADF /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F244F079A053D959E1C5C362 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -85,6 +95,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
2D0F1F294F673EF0DB5E4CA1 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -92,6 +103,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
108157F896CD9F637B06D7C0 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -125,6 +137,7 @@
331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
75DCDFECC7757C5159E8F0C5 /* Pods */,
);
sourceTree = "<group>";
};
@ -172,9 +185,25 @@
path = Runner;
sourceTree = "<group>";
};
75DCDFECC7757C5159E8F0C5 /* Pods */ = {
isa = PBXGroup;
children = (
24D7BEF98D33245EFB9F6A1B /* Pods-Runner.debug.xcconfig */,
F244F079A053D959E1C5C362 /* Pods-Runner.release.xcconfig */,
AB949539E0D0A8E2BDAB9ADF /* Pods-Runner.profile.xcconfig */,
96C46007EE0A4E9E1D6D74CE /* Pods-RunnerTests.debug.xcconfig */,
A604E311B663FBF4B7C54DC5 /* Pods-RunnerTests.release.xcconfig */,
81F2F315AC5109F6F5D27BE6 /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
5DAF1C60594A51D692304366 /* Pods_Runner.framework */,
E148CBDFFE42BF88E8C34DE0 /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -186,6 +215,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
A1935203066F42991FF0ED43 /* [CP] Check Pods Manifest.lock */,
331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */,
@ -204,11 +234,13 @@
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
8ECFD939A4D371A145DBA191 /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
92D754792F50A5D35F6D5AEE /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -329,6 +361,67 @@
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
8ECFD939A4D371A145DBA191 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
92D754792F50A5D35F6D5AEE /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
A1935203066F42991FF0ED43 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -380,6 +473,7 @@
/* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 96C46007EE0A4E9E1D6D74CE /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
@ -394,6 +488,7 @@
};
331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = A604E311B663FBF4B7C54DC5 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
@ -408,6 +503,7 @@
};
331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 81F2F315AC5109F6F5D27BE6 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;

View File

@ -4,4 +4,7 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -304,6 +304,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.19.0"
intl_phone_field:
dependency: "direct main"
description:
name: intl_phone_field
sha256: "73819d3dfcb68d2c85663606f6842597c3ddf6688ac777f051b17814fe767bbf"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
js:
dependency: transitive
description:
@ -316,18 +324,18 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
url: "https://pub.dev"
source: hosted
version: "10.0.5"
version: "10.0.4"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
url: "https://pub.dev"
source: hosted
version: "3.0.5"
version: "3.0.3"
leak_tracker_testing:
dependency: transitive
description:
@ -364,18 +372,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.11.1"
version: "0.8.0"
meta:
dependency: transitive
description:
name: meta
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
url: "https://pub.dev"
source: hosted
version: "1.15.0"
version: "1.12.0"
nested:
dependency: transitive
description:
@ -384,6 +392,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
number_pagination:
dependency: "direct main"
description:
name: number_pagination
sha256: "75d3a28616196e7c8df431d0fb7c48e811e462155f4cf3b5b4167b3408421327"
url: "https://pub.dev"
source: hosted
version: "1.1.6"
path:
dependency: transitive
description:
@ -593,10 +609,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
version: "0.7.0"
time_picker_spinner:
dependency: "direct main"
description:
@ -657,10 +673,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
url: "https://pub.dev"
source: hosted
version: "14.2.4"
version: "14.2.1"
web:
dependency: transitive
description:

View File

@ -52,6 +52,8 @@ dependencies:
fl_chart: ^0.69.0
uuid: ^4.4.2
time_picker_spinner: ^1.0.0
intl_phone_field: ^3.2.0
number_pagination: ^1.1.6
dev_dependencies:
flutter_test: