@dcrackel/hematournamentui 1.0.713 → 1.0.714
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/HemaTournamentUI-lib.es.js +6856 -6243
- package/dist/HemaTournamentUI-lib.umd.js +27 -27
- package/dist/hematournamentui-tailwind.css +79 -9
- package/package.json +1 -1
- package/src/index.js +43 -42
- package/src/stories/Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.stories.js +49 -0
- package/src/stories/Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.test.js +129 -0
- package/src/stories/Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.vue +165 -0
- package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.stories.js +10 -1
- package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.test.js +31 -0
- package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.vue +20 -1
- package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.test.js +22 -0
- package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.vue +27 -10
- package/src/stories/Templates/EventManagement/Bracket/Bracket.stories.js +33 -4
- package/src/stories/Templates/EventManagement/Bracket/Bracket.test.js +218 -178
- package/src/stories/Templates/EventManagement/Bracket/Bracket.vue +42 -1
- package/src/stories/Templates/EventManagement/Bracket/BracketPlannedSuggestions.js +8 -0
- package/src/stories/Templates/EventManagement/Bracket/BracketStories.test.js +121 -0
- package/src/stories/Util/bracketDirectors.js +86 -0
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
--color-white: #fff;
|
|
22
22
|
--spacing: 0.25rem;
|
|
23
23
|
--container-md: 28rem;
|
|
24
|
+
--container-xl: 36rem;
|
|
24
25
|
--text-xs: 0.75rem;
|
|
25
26
|
--text-xs--line-height: calc(1 / 0.75);
|
|
26
27
|
--text-sm: 0.875rem;
|
|
@@ -242,6 +243,9 @@
|
|
|
242
243
|
.inset-0 {
|
|
243
244
|
inset: calc(var(--spacing) * 0);
|
|
244
245
|
}
|
|
246
|
+
.-top-1 {
|
|
247
|
+
top: calc(var(--spacing) * -1);
|
|
248
|
+
}
|
|
245
249
|
.-top-4 {
|
|
246
250
|
top: calc(var(--spacing) * -4);
|
|
247
251
|
}
|
|
@@ -290,6 +294,9 @@
|
|
|
290
294
|
.left-1 {
|
|
291
295
|
left: calc(var(--spacing) * 1);
|
|
292
296
|
}
|
|
297
|
+
.left-1\/2 {
|
|
298
|
+
left: calc(1 / 2 * 100%);
|
|
299
|
+
}
|
|
293
300
|
.left-11 {
|
|
294
301
|
left: calc(var(--spacing) * 11);
|
|
295
302
|
}
|
|
@@ -311,6 +318,9 @@
|
|
|
311
318
|
.z-50 {
|
|
312
319
|
z-index: 50;
|
|
313
320
|
}
|
|
321
|
+
.z-\[100\] {
|
|
322
|
+
z-index: 100;
|
|
323
|
+
}
|
|
314
324
|
.order-first {
|
|
315
325
|
order: -9999;
|
|
316
326
|
}
|
|
@@ -359,14 +369,17 @@
|
|
|
359
369
|
.m-4 {
|
|
360
370
|
margin: calc(var(--spacing) * 4);
|
|
361
371
|
}
|
|
372
|
+
.m-auto {
|
|
373
|
+
margin: auto;
|
|
374
|
+
}
|
|
362
375
|
.mx-1 {
|
|
363
376
|
margin-inline: calc(var(--spacing) * 1);
|
|
364
377
|
}
|
|
365
378
|
.mx-2 {
|
|
366
379
|
margin-inline: calc(var(--spacing) * 2);
|
|
367
380
|
}
|
|
368
|
-
.mx-
|
|
369
|
-
margin-inline: calc(var(--spacing) *
|
|
381
|
+
.mx-3 {
|
|
382
|
+
margin-inline: calc(var(--spacing) * 3);
|
|
370
383
|
}
|
|
371
384
|
.mx-4 {
|
|
372
385
|
margin-inline: calc(var(--spacing) * 4);
|
|
@@ -656,6 +669,9 @@
|
|
|
656
669
|
.max-h-60 {
|
|
657
670
|
max-height: calc(var(--spacing) * 60);
|
|
658
671
|
}
|
|
672
|
+
.max-h-\[calc\(100dvh-2rem\)\] {
|
|
673
|
+
max-height: calc(100dvh - 2rem);
|
|
674
|
+
}
|
|
659
675
|
.max-h-screen {
|
|
660
676
|
max-height: 100vh;
|
|
661
677
|
}
|
|
@@ -665,6 +681,9 @@
|
|
|
665
681
|
.min-h-5 {
|
|
666
682
|
min-height: calc(var(--spacing) * 5);
|
|
667
683
|
}
|
|
684
|
+
.min-h-11 {
|
|
685
|
+
min-height: calc(var(--spacing) * 11);
|
|
686
|
+
}
|
|
668
687
|
.min-h-16 {
|
|
669
688
|
min-height: calc(var(--spacing) * 16);
|
|
670
689
|
}
|
|
@@ -821,6 +840,9 @@
|
|
|
821
840
|
.w-\[900px\] {
|
|
822
841
|
width: 900px;
|
|
823
842
|
}
|
|
843
|
+
.w-\[calc\(100\%-2rem\)\] {
|
|
844
|
+
width: calc(100% - 2rem);
|
|
845
|
+
}
|
|
824
846
|
.w-full {
|
|
825
847
|
width: 100%;
|
|
826
848
|
}
|
|
@@ -839,12 +861,18 @@
|
|
|
839
861
|
.max-w-md {
|
|
840
862
|
max-width: var(--container-md);
|
|
841
863
|
}
|
|
864
|
+
.max-w-xl {
|
|
865
|
+
max-width: var(--container-xl);
|
|
866
|
+
}
|
|
842
867
|
.min-w-0 {
|
|
843
868
|
min-width: calc(var(--spacing) * 0);
|
|
844
869
|
}
|
|
845
870
|
.min-w-20 {
|
|
846
871
|
min-width: calc(var(--spacing) * 20);
|
|
847
872
|
}
|
|
873
|
+
.min-w-36 {
|
|
874
|
+
min-width: calc(var(--spacing) * 36);
|
|
875
|
+
}
|
|
848
876
|
.min-w-40 {
|
|
849
877
|
min-width: calc(var(--spacing) * 40);
|
|
850
878
|
}
|
|
@@ -886,6 +914,10 @@
|
|
|
886
914
|
--tw-border-spacing-y: calc(var(--spacing) * 1);
|
|
887
915
|
border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y);
|
|
888
916
|
}
|
|
917
|
+
.-translate-x-1\/2 {
|
|
918
|
+
--tw-translate-x: calc(calc(1 / 2 * 100%) * -1);
|
|
919
|
+
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
920
|
+
}
|
|
889
921
|
.translate-x-full {
|
|
890
922
|
--tw-translate-x: 100%;
|
|
891
923
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
@@ -1594,6 +1626,9 @@
|
|
|
1594
1626
|
font-size: var(--text-xs);
|
|
1595
1627
|
line-height: var(--tw-leading, var(--text-xs--line-height));
|
|
1596
1628
|
}
|
|
1629
|
+
.text-\[9px\] {
|
|
1630
|
+
font-size: 9px;
|
|
1631
|
+
}
|
|
1597
1632
|
.text-\[12rem\] {
|
|
1598
1633
|
font-size: 12rem;
|
|
1599
1634
|
}
|
|
@@ -1635,6 +1670,12 @@
|
|
|
1635
1670
|
.text-nowrap {
|
|
1636
1671
|
text-wrap: nowrap;
|
|
1637
1672
|
}
|
|
1673
|
+
.break-words {
|
|
1674
|
+
overflow-wrap: break-word;
|
|
1675
|
+
}
|
|
1676
|
+
.wrap-break-word {
|
|
1677
|
+
overflow-wrap: break-word;
|
|
1678
|
+
}
|
|
1638
1679
|
.whitespace-nowrap {
|
|
1639
1680
|
white-space: nowrap;
|
|
1640
1681
|
}
|
|
@@ -1737,6 +1778,9 @@
|
|
|
1737
1778
|
.text-toggleBackground {
|
|
1738
1779
|
color: #A9C4D6;
|
|
1739
1780
|
}
|
|
1781
|
+
.text-white {
|
|
1782
|
+
color: var(--color-white);
|
|
1783
|
+
}
|
|
1740
1784
|
.uppercase {
|
|
1741
1785
|
text-transform: uppercase;
|
|
1742
1786
|
}
|
|
@@ -1746,6 +1790,9 @@
|
|
|
1746
1790
|
.underline {
|
|
1747
1791
|
text-decoration-line: underline;
|
|
1748
1792
|
}
|
|
1793
|
+
.accent-bright {
|
|
1794
|
+
accent-color: #6190f2;
|
|
1795
|
+
}
|
|
1749
1796
|
.accent-primary {
|
|
1750
1797
|
accent-color: #141B33;
|
|
1751
1798
|
}
|
|
@@ -1873,6 +1920,19 @@
|
|
|
1873
1920
|
-webkit-user-select: none;
|
|
1874
1921
|
user-select: none;
|
|
1875
1922
|
}
|
|
1923
|
+
.backdrop\:bg-black\/20 {
|
|
1924
|
+
&::backdrop {
|
|
1925
|
+
background-color: color-mix(in srgb, #000 20%, transparent);
|
|
1926
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
1927
|
+
background-color: color-mix(in oklab, var(--color-black) 20%, transparent);
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
.backdrop\:bg-secondary\/60 {
|
|
1932
|
+
&::backdrop {
|
|
1933
|
+
background-color: color-mix(in oklab, #2B324B 60%, transparent);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1876
1936
|
.last\:border-b-0 {
|
|
1877
1937
|
&:last-child {
|
|
1878
1938
|
border-bottom-style: var(--tw-border-style);
|
|
@@ -2115,6 +2175,11 @@
|
|
|
2115
2175
|
border-color: #6190f2;
|
|
2116
2176
|
}
|
|
2117
2177
|
}
|
|
2178
|
+
.focus-visible\:bg-dropdownSelect {
|
|
2179
|
+
&:focus-visible {
|
|
2180
|
+
background-color: #D5E4EE;
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2118
2183
|
.focus-visible\:ring-2 {
|
|
2119
2184
|
&:focus-visible {
|
|
2120
2185
|
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
|
@@ -2152,6 +2217,11 @@
|
|
|
2152
2217
|
opacity: 40%;
|
|
2153
2218
|
}
|
|
2154
2219
|
}
|
|
2220
|
+
.disabled\:opacity-50 {
|
|
2221
|
+
&:disabled {
|
|
2222
|
+
opacity: 50%;
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2155
2225
|
.md\:m-9 {
|
|
2156
2226
|
@media (width >= 48rem) {
|
|
2157
2227
|
margin: calc(var(--spacing) * 9);
|
|
@@ -2272,6 +2342,11 @@
|
|
|
2272
2342
|
display: none;
|
|
2273
2343
|
}
|
|
2274
2344
|
}
|
|
2345
|
+
.md\:inline-block {
|
|
2346
|
+
@media (width >= 48rem) {
|
|
2347
|
+
display: inline-block;
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2275
2350
|
.md\:h-7 {
|
|
2276
2351
|
@media (width >= 48rem) {
|
|
2277
2352
|
height: calc(var(--spacing) * 7);
|
|
@@ -2397,9 +2472,9 @@
|
|
|
2397
2472
|
flex-direction: row;
|
|
2398
2473
|
}
|
|
2399
2474
|
}
|
|
2400
|
-
.md\:flex-
|
|
2475
|
+
.md\:flex-wrap {
|
|
2401
2476
|
@media (width >= 48rem) {
|
|
2402
|
-
flex-wrap:
|
|
2477
|
+
flex-wrap: wrap;
|
|
2403
2478
|
}
|
|
2404
2479
|
}
|
|
2405
2480
|
.md\:items-stretch {
|
|
@@ -2407,11 +2482,6 @@
|
|
|
2407
2482
|
align-items: stretch;
|
|
2408
2483
|
}
|
|
2409
2484
|
}
|
|
2410
|
-
.md\:justify-start {
|
|
2411
|
-
@media (width >= 48rem) {
|
|
2412
|
-
justify-content: flex-start;
|
|
2413
|
-
}
|
|
2414
|
-
}
|
|
2415
2485
|
.md\:rounded-lg {
|
|
2416
2486
|
@media (width >= 48rem) {
|
|
2417
2487
|
border-radius: var(--radius-lg);
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,42 +1,43 @@
|
|
|
1
|
-
export { default as BaseIcon } from './stories/Atoms/Icon/BaseIcon.vue';
|
|
2
|
-
export { default as BaseInput } from './stories/Atoms/Input/BaseInput.vue';
|
|
3
|
-
export { default as BaseRadioGroup } from './stories/Atoms/RadioGroup/BaseRadioGroup.vue';
|
|
4
|
-
export { default as BaseTag } from './stories/Atoms/Tag/BaseTag.vue';
|
|
5
|
-
export { default as BaseText } from './stories/Atoms/Text/BaseText.vue';
|
|
6
|
-
|
|
7
|
-
export { default as Breadcrumb } from './stories/Molecules/Breadcrumb/Admin/Breadcrumb.vue';
|
|
8
|
-
export { default as BaseButton } from './stories/Molecules/Buttons/BaseButton/BaseButton.vue';
|
|
9
|
-
export { default as FilterAndSortBar } from './stories/Molecules/Filters/FilterAndSortBar/FilterAndSortBar.vue';
|
|
10
|
-
export { default as FilterUpcomingPast } from './stories/Molecules/Filters/FilterUpcomingPast/FilterUpcomingPast.vue';
|
|
11
|
-
export { default as BaseModal } from './stories/Molecules/Modals/BaseModal/BaseModal.vue';
|
|
12
|
-
export { default as
|
|
13
|
-
export { default as
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export { default as
|
|
17
|
-
export { default as
|
|
18
|
-
export { default as
|
|
19
|
-
export { default as
|
|
20
|
-
export { default as
|
|
21
|
-
export { default as
|
|
22
|
-
export { default as
|
|
23
|
-
export { default as
|
|
24
|
-
export { default as
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export { default as
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
export { default as
|
|
31
|
-
export { default as
|
|
32
|
-
export { default as
|
|
33
|
-
export { default as
|
|
34
|
-
export { default as
|
|
35
|
-
export { default as
|
|
36
|
-
export { default as
|
|
37
|
-
export { default as
|
|
38
|
-
export { default as
|
|
39
|
-
export { default as
|
|
40
|
-
export { default as
|
|
41
|
-
export { default as
|
|
42
|
-
export { default as
|
|
1
|
+
export { default as BaseIcon } from './stories/Atoms/Icon/BaseIcon.vue';
|
|
2
|
+
export { default as BaseInput } from './stories/Atoms/Input/BaseInput.vue';
|
|
3
|
+
export { default as BaseRadioGroup } from './stories/Atoms/RadioGroup/BaseRadioGroup.vue';
|
|
4
|
+
export { default as BaseTag } from './stories/Atoms/Tag/BaseTag.vue';
|
|
5
|
+
export { default as BaseText } from './stories/Atoms/Text/BaseText.vue';
|
|
6
|
+
|
|
7
|
+
export { default as Breadcrumb } from './stories/Molecules/Breadcrumb/Admin/Breadcrumb.vue';
|
|
8
|
+
export { default as BaseButton } from './stories/Molecules/Buttons/BaseButton/BaseButton.vue';
|
|
9
|
+
export { default as FilterAndSortBar } from './stories/Molecules/Filters/FilterAndSortBar/FilterAndSortBar.vue';
|
|
10
|
+
export { default as FilterUpcomingPast } from './stories/Molecules/Filters/FilterUpcomingPast/FilterUpcomingPast.vue';
|
|
11
|
+
export { default as BaseModal } from './stories/Molecules/Modals/BaseModal/BaseModal.vue';
|
|
12
|
+
export { default as BracketDirectorsModal } from './stories/Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.vue';
|
|
13
|
+
export { default as LoadingModal } from './stories/Molecules/Modals/LoadingModal/LoadingModal.vue';
|
|
14
|
+
export { default as EventWizardModal } from './stories/Molecules/Modals/EventWizardModal/EventWizardModal.vue';
|
|
15
|
+
|
|
16
|
+
export { default as AddressAutocomplete } from './stories/Organisms/ComplexInputs/AddressAutocomplete/AddressAutocomplete.vue';
|
|
17
|
+
export { default as DatePicker } from './stories/Organisms/ComplexInputs/DatePicker/DatePicker.vue';
|
|
18
|
+
export { default as DropDownMenu } from './stories/Organisms/ComplexInputs/DropDown/DropDownMenu.vue';
|
|
19
|
+
export { default as GridContainer } from './stories/Organisms/Grids/GridContainer.vue';
|
|
20
|
+
export { default as PoolGrid } from './stories/Organisms/Containers/PoolGrid/PoolGrid.vue';
|
|
21
|
+
export { default as BracketDirectorAssignmentCard } from './stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.vue';
|
|
22
|
+
export { default as BracketDirectorAssignmentPanel } from './stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.vue';
|
|
23
|
+
export { default as ImageCropper } from './stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.vue';
|
|
24
|
+
export { default as TextBoxEditor } from './stories/Organisms/ComplexInputs/TextBoxEditor/TextBoxEditor.vue';
|
|
25
|
+
export { default as EditEventWizard } from './stories/Organisms/Wizards/EditEventWizard/EditEventWizard.vue';
|
|
26
|
+
|
|
27
|
+
export { default as TournamentManagement } from './stories/Templates/TournamentManagement/TournamentManagement.vue';
|
|
28
|
+
export { default as ListEvents } from './stories/Templates/TournamentManagement/ListEvents/ListEvents.vue';
|
|
29
|
+
|
|
30
|
+
export { default as AdminLeftMenu } from './stories/Templates/Menus/Admin/AdminLeftMenu.vue';
|
|
31
|
+
export { default as EventAttendance } from './stories/Templates/EventManagement/EventAttendance/EventAttendance.vue';
|
|
32
|
+
export { default as PoolManagement } from './stories/Templates/EventManagement/PoolManagement/PoolManagement.vue';
|
|
33
|
+
export { default as StaffList } from './stories/Templates/EventManagement/StaffList/StaffList.vue';
|
|
34
|
+
export { default as PoolLive } from './stories/Templates/EventManagement/PoolLive/PoolLive.vue';
|
|
35
|
+
export { default as PoolResults } from './stories/Templates/EventManagement/PoolResults/PoolResults.vue';
|
|
36
|
+
export { default as Bracket } from './stories/Templates/EventManagement/Bracket/Bracket.vue';
|
|
37
|
+
export { default as EditEventInfo } from './stories/Templates/EventManagement/EditEventInfo/EditEventInfo.vue';
|
|
38
|
+
export { default as EventFinalResults } from './stories/Templates/EventManagement/EventFinalResults/EventFinalResults.vue';
|
|
39
|
+
export { default as KioskDisplay } from './stories/Templates/EventManagement/KioskDisplay/KioskDisplay.vue';
|
|
40
|
+
export { default as PersonManagement } from './stories/Templates/PersonManagement/PersonManagement.vue';
|
|
41
|
+
export { default as ClubManagement } from './stories/Templates/ClubManagement/ClubManagement.vue';
|
|
42
|
+
export { default as Assignment } from './stories/Templates/Assignment/Assignment.vue';
|
|
43
|
+
export { default as PenaltyReport } from './stories/Templates/EventManagement/Penalties/PenaltyReport.vue';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ref, watch } from 'vue';
|
|
2
|
+
import BracketDirectorsModal from './BracketDirectorsModal.vue';
|
|
3
|
+
import {
|
|
4
|
+
availableDirectors,
|
|
5
|
+
availableRings,
|
|
6
|
+
plannedDirectingStations
|
|
7
|
+
} from '../../../Templates/EventManagement/Bracket/BracketPlannedSuggestions.js';
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
title: 'Molecules/Modals/BracketDirectorsModal',
|
|
11
|
+
component: BracketDirectorsModal,
|
|
12
|
+
args: { show: true, availableDirectors, rings: availableRings, stations: plannedDirectingStations.slice(0, 2) },
|
|
13
|
+
argTypes: {
|
|
14
|
+
show: { control: 'boolean' },
|
|
15
|
+
availableDirectors: { control: 'object' },
|
|
16
|
+
rings: { control: 'object' },
|
|
17
|
+
stations: { control: 'object' }
|
|
18
|
+
},
|
|
19
|
+
render: args => ({
|
|
20
|
+
components: { BracketDirectorsModal },
|
|
21
|
+
setup() {
|
|
22
|
+
const show = ref(args.show);
|
|
23
|
+
const stations = ref(args.stations);
|
|
24
|
+
watch(
|
|
25
|
+
() => args.show,
|
|
26
|
+
value => {
|
|
27
|
+
show.value = value;
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
watch(
|
|
31
|
+
() => args.stations,
|
|
32
|
+
value => {
|
|
33
|
+
stations.value = value;
|
|
34
|
+
},
|
|
35
|
+
{ deep: true }
|
|
36
|
+
);
|
|
37
|
+
function saveStations(value) {
|
|
38
|
+
stations.value = value;
|
|
39
|
+
}
|
|
40
|
+
return { args, show, stations, saveStations };
|
|
41
|
+
},
|
|
42
|
+
template:
|
|
43
|
+
'<div><button type="button" class="rounded bg-primary px-4 py-2 text-white" @click="show = true">Manage directors</button><BracketDirectorsModal v-bind="args" v-model:show="show" :stations="stations" @submit:stations="saveStations" /></div>'
|
|
44
|
+
})
|
|
45
|
+
};
|
|
46
|
+
export const StaffAndPoolDefaults = {};
|
|
47
|
+
export const NoSelectedDirectors = { args: { stations: [] } };
|
|
48
|
+
export const NoAvailableStaff = { args: { stations: [], availableDirectors: [] } };
|
|
49
|
+
export const NoRings = { args: { rings: [] } };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { nextTick } from 'vue';
|
|
3
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
4
|
+
import BracketDirectorsModal from './BracketDirectorsModal.vue';
|
|
5
|
+
|
|
6
|
+
const directors = [
|
|
7
|
+
{ PersonId: 1, DisplayName: 'Dave Smith', Color: '#2563eb', DefaultRingId: 1 },
|
|
8
|
+
{ PersonId: 2, DisplayName: 'Terry Jones', Color: '#dc2626' }
|
|
9
|
+
];
|
|
10
|
+
const rings = [
|
|
11
|
+
{ RingId: 1, RingName: 'Ring 1' },
|
|
12
|
+
{ RingId: 2, RingName: 'Ring 2' },
|
|
13
|
+
{ RingId: 3, RingName: 'Ring 3' }
|
|
14
|
+
];
|
|
15
|
+
const station = {
|
|
16
|
+
RingId: 1,
|
|
17
|
+
RingName: 'Ring 1',
|
|
18
|
+
Color: '#2563eb',
|
|
19
|
+
Director: { PersonId: 1, DisplayName: 'Dave Smith' }
|
|
20
|
+
};
|
|
21
|
+
const wrappers = [];
|
|
22
|
+
const mountModal = async (props = {}) => {
|
|
23
|
+
const wrapper = mount(BracketDirectorsModal, {
|
|
24
|
+
attachTo: document.body,
|
|
25
|
+
global: { stubs: { Teleport: true } },
|
|
26
|
+
props: {
|
|
27
|
+
show: true,
|
|
28
|
+
availableDirectors: structuredClone(directors),
|
|
29
|
+
rings: structuredClone(rings),
|
|
30
|
+
stations: [structuredClone(station)],
|
|
31
|
+
...props
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
wrappers.push(wrapper);
|
|
35
|
+
await nextTick();
|
|
36
|
+
return wrapper;
|
|
37
|
+
};
|
|
38
|
+
const dialog = () => document.querySelector('[data-testid="bracket-directors-modal"]');
|
|
39
|
+
const checkbox = (wrapper, id) => wrapper.get('[data-director-id="' + id + '"]');
|
|
40
|
+
const ringSelect = (wrapper, id) => wrapper.get('[data-ring-director-id="' + id + '"]');
|
|
41
|
+
afterEach(() => wrappers.splice(0).forEach(wrapper => wrapper.unmount()));
|
|
42
|
+
|
|
43
|
+
describe('BracketDirectorsModal', () => {
|
|
44
|
+
it('starts from supplied DE assignments and adds a director who has no pool assignment', async () => {
|
|
45
|
+
const wrapper = await mountModal();
|
|
46
|
+
expect(dialog().open).toBe(true);
|
|
47
|
+
expect(checkbox(wrapper, 1).element.checked).toBe(true);
|
|
48
|
+
expect(checkbox(wrapper, 2).element.checked).toBe(false);
|
|
49
|
+
const before = JSON.parse(JSON.stringify(wrapper.props()));
|
|
50
|
+
await checkbox(wrapper, 2).setValue(true);
|
|
51
|
+
await ringSelect(wrapper, 2).setValue('2');
|
|
52
|
+
await wrapper.get('form').trigger('submit');
|
|
53
|
+
const payload = wrapper.emitted('submit:stations')[0][0];
|
|
54
|
+
expect(payload.map(item => [item.Director.PersonId, item.RingId, item.Color])).toEqual([
|
|
55
|
+
[1, 1, '#2563eb'],
|
|
56
|
+
[2, 2, '#dc2626']
|
|
57
|
+
]);
|
|
58
|
+
expect(wrapper.props()).toEqual(before);
|
|
59
|
+
payload[0].Director.DisplayName = 'Changed';
|
|
60
|
+
expect(wrapper.props('stations')[0].Director.DisplayName).toBe('Dave Smith');
|
|
61
|
+
expect(wrapper.emitted('update:show')).toEqual([[false]]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('removes directors by unchecking them and can save an empty roster', async () => {
|
|
65
|
+
const wrapper = await mountModal();
|
|
66
|
+
await checkbox(wrapper, 1).setValue(false);
|
|
67
|
+
await wrapper.get('form').trigger('submit');
|
|
68
|
+
expect(wrapper.emitted('submit:stations')).toEqual([[[]]]);
|
|
69
|
+
expect(wrapper.props('stations')).toHaveLength(1);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('allows a draft ring swap and requires unique rings before saving', async () => {
|
|
73
|
+
const second = {
|
|
74
|
+
RingId: 2,
|
|
75
|
+
RingName: 'Ring 2',
|
|
76
|
+
Color: '#dc2626',
|
|
77
|
+
Director: { PersonId: 2, DisplayName: 'Terry Jones' }
|
|
78
|
+
};
|
|
79
|
+
const wrapper = await mountModal({ stations: [station, second] });
|
|
80
|
+
await ringSelect(wrapper, 1).setValue('2');
|
|
81
|
+
expect(wrapper.get('[role="alert"]').text()).toContain('different ring');
|
|
82
|
+
expect(wrapper.get('button[type="submit"]').attributes('disabled')).toBeDefined();
|
|
83
|
+
await wrapper.get('form').trigger('submit');
|
|
84
|
+
expect(wrapper.emitted('submit:stations')).toBeUndefined();
|
|
85
|
+
await ringSelect(wrapper, 2).setValue('1');
|
|
86
|
+
await wrapper.get('form').trigger('submit');
|
|
87
|
+
expect(
|
|
88
|
+
wrapper.emitted('submit:stations')[0][0].map(item => [item.Director.PersonId, item.RingId, item.Color])
|
|
89
|
+
).toEqual([
|
|
90
|
+
[1, 2, '#2563eb'],
|
|
91
|
+
[2, 1, '#dc2626']
|
|
92
|
+
]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('cancels without emitting changes and discards the draft when reopened', async () => {
|
|
96
|
+
const wrapper = await mountModal();
|
|
97
|
+
await checkbox(wrapper, 1).setValue(false);
|
|
98
|
+
await wrapper.get('[aria-label="Close director assignments"]').trigger('click');
|
|
99
|
+
expect(wrapper.emitted('submit:stations')).toBeUndefined();
|
|
100
|
+
await wrapper.setProps({ show: false });
|
|
101
|
+
expect(dialog().open).toBe(false);
|
|
102
|
+
await wrapper.setProps({ show: true });
|
|
103
|
+
expect(checkbox(wrapper, 1).element.checked).toBe(true);
|
|
104
|
+
await wrapper.get('dialog').trigger('cancel');
|
|
105
|
+
expect(wrapper.emitted('update:show')).toEqual([[false], [false]]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('accepts staff added while open without losing input drafts and reconciles parent-owned station changes', async () => {
|
|
109
|
+
const wrapper = await mountModal();
|
|
110
|
+
await checkbox(wrapper, 2).setValue(true);
|
|
111
|
+
await ringSelect(wrapper, 2).setValue('2');
|
|
112
|
+
await wrapper.setProps({ availableDirectors: [...directors, { PersonId: 3, DisplayName: 'Michael Cord' }] });
|
|
113
|
+
expect(checkbox(wrapper, 2).element.checked).toBe(true);
|
|
114
|
+
expect(ringSelect(wrapper, 2).element.value).toBe('2');
|
|
115
|
+
expect(checkbox(wrapper, 3).element.checked).toBe(false);
|
|
116
|
+
await wrapper.setProps({ stations: [] });
|
|
117
|
+
expect(checkbox(wrapper, 1).element.checked).toBe(false);
|
|
118
|
+
expect(checkbox(wrapper, 2).element.checked).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('degrades gracefully for missing staff or rings and includes previously selected directors', async () => {
|
|
122
|
+
const wrapper = await mountModal({ availableDirectors: [null, {}, { PersonId: 0 }], rings: [] });
|
|
123
|
+
expect(checkbox(wrapper, 1).element.checked).toBe(true);
|
|
124
|
+
expect(wrapper.get('[role="alert"]').text()).toContain('Choose a ring');
|
|
125
|
+
expect(wrapper.get('button[type="submit"]').attributes('disabled')).toBeDefined();
|
|
126
|
+
await wrapper.setProps({ stations: [] });
|
|
127
|
+
expect(wrapper.text()).toContain('No directors available.');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Teleport to="body">
|
|
3
|
+
<dialog
|
|
4
|
+
ref="dialog"
|
|
5
|
+
aria-label="DE directors"
|
|
6
|
+
data-testid="bracket-directors-modal"
|
|
7
|
+
class="fixed inset-0 m-auto max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-xl overflow-y-auto rounded-xl border border-dropdownSelect bg-neutral p-0 text-primaryHighlight shadow-xl backdrop:bg-secondary/60"
|
|
8
|
+
@cancel.prevent="close"
|
|
9
|
+
@click="close"
|
|
10
|
+
>
|
|
11
|
+
<form class="p-5" @submit.prevent="submit" @click.stop>
|
|
12
|
+
<div class="mb-4 flex items-start justify-between gap-3">
|
|
13
|
+
<div>
|
|
14
|
+
<h2 class="text-xl font-bold">Directors</h2>
|
|
15
|
+
<p class="mt-1 text-sm text-quaternary">Select DE directors and choose their rings.</p>
|
|
16
|
+
</div>
|
|
17
|
+
<button
|
|
18
|
+
type="button"
|
|
19
|
+
aria-label="Close director assignments"
|
|
20
|
+
class="flex h-8 w-8 flex-none items-center justify-center rounded text-xl text-quaternary hover:bg-dropdownSelect focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-bright"
|
|
21
|
+
@click="close"
|
|
22
|
+
>
|
|
23
|
+
×
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
<p v-if="!draft.length" class="rounded border border-dropdownSelect p-4 text-sm text-quaternary">
|
|
27
|
+
No directors available.
|
|
28
|
+
</p>
|
|
29
|
+
<div class="space-y-3">
|
|
30
|
+
<div
|
|
31
|
+
v-for="row in draft"
|
|
32
|
+
:key="row.Director.PersonId"
|
|
33
|
+
class="flex items-center gap-3 rounded-lg border border-dropdownSelect px-3 py-3"
|
|
34
|
+
>
|
|
35
|
+
<label class="flex min-w-0 flex-1 cursor-pointer items-center gap-3">
|
|
36
|
+
<input
|
|
37
|
+
v-model="row.Selected"
|
|
38
|
+
type="checkbox"
|
|
39
|
+
:data-director-id="row.Director.PersonId"
|
|
40
|
+
:aria-label="'Include ' + row.Director.DisplayName + ' in DE'"
|
|
41
|
+
class="h-5 w-5 flex-none accent-bright"
|
|
42
|
+
/>
|
|
43
|
+
<span
|
|
44
|
+
v-if="row.Color"
|
|
45
|
+
class="h-2.5 w-2.5 flex-none rounded-full"
|
|
46
|
+
:style="{ backgroundColor: row.Color }"
|
|
47
|
+
aria-hidden="true"
|
|
48
|
+
></span>
|
|
49
|
+
<span class="min-w-0 wrap-break-word text-sm font-semibold">{{ row.Director.DisplayName }}</span>
|
|
50
|
+
</label>
|
|
51
|
+
<select
|
|
52
|
+
v-model="row.RingId"
|
|
53
|
+
:disabled="!row.Selected"
|
|
54
|
+
:data-ring-director-id="row.Director.PersonId"
|
|
55
|
+
:aria-label="'Ring for ' + row.Director.DisplayName"
|
|
56
|
+
class="w-28 flex-none rounded border border-dropdownSelect bg-white px-2 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-bright disabled:opacity-50"
|
|
57
|
+
>
|
|
58
|
+
<option value="">Choose ring</option>
|
|
59
|
+
<option v-for="ring in ringOptions" :key="ring.RingId" :value="ring.RingId" :disabled="ring.Disabled">
|
|
60
|
+
{{ ring.RingName }}
|
|
61
|
+
</option>
|
|
62
|
+
</select>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
<p v-if="error" role="alert" class="mt-3 text-sm text-alarm">{{ error }}</p>
|
|
66
|
+
<div class="mt-5 flex justify-end gap-2">
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
class="rounded border border-dropdownSelect px-4 py-2 text-sm hover:bg-dropdownSelect focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-bright"
|
|
70
|
+
@click="close"
|
|
71
|
+
>
|
|
72
|
+
Cancel
|
|
73
|
+
</button>
|
|
74
|
+
<button
|
|
75
|
+
type="submit"
|
|
76
|
+
:disabled="Boolean(error)"
|
|
77
|
+
class="rounded bg-primary px-4 py-2 text-sm font-semibold text-neutral hover:bg-primaryHighlight focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-bright disabled:cursor-not-allowed disabled:opacity-50"
|
|
78
|
+
>
|
|
79
|
+
Save assignments
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
</form>
|
|
83
|
+
</dialog>
|
|
84
|
+
</Teleport>
|
|
85
|
+
</template>
|
|
86
|
+
|
|
87
|
+
<script>
|
|
88
|
+
import {
|
|
89
|
+
createDirectorDraft,
|
|
90
|
+
reconcileDirectorDraft,
|
|
91
|
+
createDirectorStations,
|
|
92
|
+
getDirectorDraftError,
|
|
93
|
+
getRingOptions
|
|
94
|
+
} from '../../../Util/bracketDirectors.js';
|
|
95
|
+
|
|
96
|
+
export default {
|
|
97
|
+
name: 'BracketDirectorsModal',
|
|
98
|
+
emits: ['update:show', 'submit:stations'],
|
|
99
|
+
props: {
|
|
100
|
+
show: { type: Boolean, default: false },
|
|
101
|
+
availableDirectors: { type: Array, default: () => [] },
|
|
102
|
+
rings: { type: Array, default: () => [] },
|
|
103
|
+
stations: { type: Array, default: () => [] }
|
|
104
|
+
},
|
|
105
|
+
data() {
|
|
106
|
+
return { draft: [] };
|
|
107
|
+
},
|
|
108
|
+
computed: {
|
|
109
|
+
ringOptions() {
|
|
110
|
+
return getRingOptions(this.rings);
|
|
111
|
+
},
|
|
112
|
+
error() {
|
|
113
|
+
return getDirectorDraftError(this.draft, this.rings);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
watch: {
|
|
117
|
+
show: {
|
|
118
|
+
immediate: true,
|
|
119
|
+
handler(value) {
|
|
120
|
+
if (value) this.resetDraft();
|
|
121
|
+
this.$nextTick(this.syncDialog);
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
stations: {
|
|
125
|
+
deep: true,
|
|
126
|
+
handler() {
|
|
127
|
+
if (this.show) this.resetDraft();
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
availableDirectors: {
|
|
131
|
+
deep: true,
|
|
132
|
+
handler() {
|
|
133
|
+
if (!this.show) return;
|
|
134
|
+
this.draft = reconcileDirectorDraft(this.availableDirectors, this.stations, this.draft);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
mounted() {
|
|
139
|
+
this.syncDialog();
|
|
140
|
+
},
|
|
141
|
+
methods: {
|
|
142
|
+
resetDraft() {
|
|
143
|
+
this.draft = createDirectorDraft(this.availableDirectors, this.stations);
|
|
144
|
+
},
|
|
145
|
+
syncDialog() {
|
|
146
|
+
const dialog = this.$refs.dialog;
|
|
147
|
+
if (!dialog) return;
|
|
148
|
+
if (this.show && !dialog.open) {
|
|
149
|
+
dialog.showModal();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (!this.show && dialog.open) dialog.close();
|
|
153
|
+
},
|
|
154
|
+
close() {
|
|
155
|
+
this.$emit('update:show', false);
|
|
156
|
+
},
|
|
157
|
+
submit() {
|
|
158
|
+
const stations = createDirectorStations(this.draft, this.rings);
|
|
159
|
+
if (!stations) return;
|
|
160
|
+
this.$emit('submit:stations', stations);
|
|
161
|
+
this.close();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
</script>
|