@dcrackel/hematournamentui 1.0.713 → 1.0.715

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.
Files changed (20) hide show
  1. package/README.md +14 -0
  2. package/dist/HemaTournamentUI-lib.es.js +6917 -6255
  3. package/dist/HemaTournamentUI-lib.umd.js +27 -27
  4. package/dist/hematournamentui-tailwind.css +109 -18
  5. package/package.json +1 -1
  6. package/src/index.js +43 -42
  7. package/src/stories/Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.stories.js +49 -0
  8. package/src/stories/Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.test.js +129 -0
  9. package/src/stories/Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.vue +165 -0
  10. package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.stories.js +10 -1
  11. package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.test.js +31 -0
  12. package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.vue +20 -1
  13. package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.test.js +38 -0
  14. package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.vue +63 -9
  15. package/src/stories/Templates/EventManagement/Bracket/Bracket.stories.js +46 -4
  16. package/src/stories/Templates/EventManagement/Bracket/Bracket.test.js +244 -178
  17. package/src/stories/Templates/EventManagement/Bracket/Bracket.vue +48 -3
  18. package/src/stories/Templates/EventManagement/Bracket/BracketPlannedSuggestions.js +8 -0
  19. package/src/stories/Templates/EventManagement/Bracket/BracketStories.test.js +121 -0
  20. 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-2\.5 {
369
- margin-inline: calc(var(--spacing) * 2.5);
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,12 @@
665
681
  .min-h-5 {
666
682
  min-height: calc(var(--spacing) * 5);
667
683
  }
684
+ .min-h-9 {
685
+ min-height: calc(var(--spacing) * 9);
686
+ }
687
+ .min-h-11 {
688
+ min-height: calc(var(--spacing) * 11);
689
+ }
668
690
  .min-h-16 {
669
691
  min-height: calc(var(--spacing) * 16);
670
692
  }
@@ -680,6 +702,9 @@
680
702
  .min-h-\[320px\] {
681
703
  min-height: 320px;
682
704
  }
705
+ .min-h-full {
706
+ min-height: 100%;
707
+ }
683
708
  .min-h-screen {
684
709
  min-height: 100vh;
685
710
  }
@@ -821,6 +846,9 @@
821
846
  .w-\[900px\] {
822
847
  width: 900px;
823
848
  }
849
+ .w-\[calc\(100\%-2rem\)\] {
850
+ width: calc(100% - 2rem);
851
+ }
824
852
  .w-full {
825
853
  width: 100%;
826
854
  }
@@ -839,12 +867,18 @@
839
867
  .max-w-md {
840
868
  max-width: var(--container-md);
841
869
  }
870
+ .max-w-xl {
871
+ max-width: var(--container-xl);
872
+ }
842
873
  .min-w-0 {
843
874
  min-width: calc(var(--spacing) * 0);
844
875
  }
845
876
  .min-w-20 {
846
877
  min-width: calc(var(--spacing) * 20);
847
878
  }
879
+ .min-w-36 {
880
+ min-width: calc(var(--spacing) * 36);
881
+ }
848
882
  .min-w-40 {
849
883
  min-width: calc(var(--spacing) * 40);
850
884
  }
@@ -866,6 +900,9 @@
866
900
  .flex-1 {
867
901
  flex: 1;
868
902
  }
903
+ .flex-auto {
904
+ flex: auto;
905
+ }
869
906
  .flex-none {
870
907
  flex: none;
871
908
  }
@@ -886,6 +923,10 @@
886
923
  --tw-border-spacing-y: calc(var(--spacing) * 1);
887
924
  border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y);
888
925
  }
926
+ .-translate-x-1\/2 {
927
+ --tw-translate-x: calc(calc(1 / 2 * 100%) * -1);
928
+ translate: var(--tw-translate-x) var(--tw-translate-y);
929
+ }
889
930
  .translate-x-full {
890
931
  --tw-translate-x: 100%;
891
932
  translate: var(--tw-translate-x) var(--tw-translate-y);
@@ -1594,6 +1635,9 @@
1594
1635
  font-size: var(--text-xs);
1595
1636
  line-height: var(--tw-leading, var(--text-xs--line-height));
1596
1637
  }
1638
+ .text-\[9px\] {
1639
+ font-size: 9px;
1640
+ }
1597
1641
  .text-\[12rem\] {
1598
1642
  font-size: 12rem;
1599
1643
  }
@@ -1635,6 +1679,12 @@
1635
1679
  .text-nowrap {
1636
1680
  text-wrap: nowrap;
1637
1681
  }
1682
+ .break-words {
1683
+ overflow-wrap: break-word;
1684
+ }
1685
+ .wrap-break-word {
1686
+ overflow-wrap: break-word;
1687
+ }
1638
1688
  .whitespace-nowrap {
1639
1689
  white-space: nowrap;
1640
1690
  }
@@ -1737,6 +1787,9 @@
1737
1787
  .text-toggleBackground {
1738
1788
  color: #A9C4D6;
1739
1789
  }
1790
+ .text-white {
1791
+ color: var(--color-white);
1792
+ }
1740
1793
  .uppercase {
1741
1794
  text-transform: uppercase;
1742
1795
  }
@@ -1746,6 +1799,9 @@
1746
1799
  .underline {
1747
1800
  text-decoration-line: underline;
1748
1801
  }
1802
+ .accent-bright {
1803
+ accent-color: #6190f2;
1804
+ }
1749
1805
  .accent-primary {
1750
1806
  accent-color: #141B33;
1751
1807
  }
@@ -1873,6 +1929,19 @@
1873
1929
  -webkit-user-select: none;
1874
1930
  user-select: none;
1875
1931
  }
1932
+ .backdrop\:bg-black\/20 {
1933
+ &::backdrop {
1934
+ background-color: color-mix(in srgb, #000 20%, transparent);
1935
+ @supports (color: color-mix(in lab, red, red)) {
1936
+ background-color: color-mix(in oklab, var(--color-black) 20%, transparent);
1937
+ }
1938
+ }
1939
+ }
1940
+ .backdrop\:bg-secondary\/60 {
1941
+ &::backdrop {
1942
+ background-color: color-mix(in oklab, #2B324B 60%, transparent);
1943
+ }
1944
+ }
1876
1945
  .last\:border-b-0 {
1877
1946
  &:last-child {
1878
1947
  border-bottom-style: var(--tw-border-style);
@@ -2115,6 +2184,11 @@
2115
2184
  border-color: #6190f2;
2116
2185
  }
2117
2186
  }
2187
+ .focus-visible\:bg-dropdownSelect {
2188
+ &:focus-visible {
2189
+ background-color: #D5E4EE;
2190
+ }
2191
+ }
2118
2192
  .focus-visible\:ring-2 {
2119
2193
  &:focus-visible {
2120
2194
  --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
@@ -2152,6 +2226,11 @@
2152
2226
  opacity: 40%;
2153
2227
  }
2154
2228
  }
2229
+ .disabled\:opacity-50 {
2230
+ &:disabled {
2231
+ opacity: 50%;
2232
+ }
2233
+ }
2155
2234
  .md\:m-9 {
2156
2235
  @media (width >= 48rem) {
2157
2236
  margin: calc(var(--spacing) * 9);
@@ -2272,6 +2351,11 @@
2272
2351
  display: none;
2273
2352
  }
2274
2353
  }
2354
+ .md\:inline-block {
2355
+ @media (width >= 48rem) {
2356
+ display: inline-block;
2357
+ }
2358
+ }
2275
2359
  .md\:h-7 {
2276
2360
  @media (width >= 48rem) {
2277
2361
  height: calc(var(--spacing) * 7);
@@ -2397,9 +2481,9 @@
2397
2481
  flex-direction: row;
2398
2482
  }
2399
2483
  }
2400
- .md\:flex-nowrap {
2484
+ .md\:flex-wrap {
2401
2485
  @media (width >= 48rem) {
2402
- flex-wrap: nowrap;
2486
+ flex-wrap: wrap;
2403
2487
  }
2404
2488
  }
2405
2489
  .md\:items-stretch {
@@ -2407,11 +2491,6 @@
2407
2491
  align-items: stretch;
2408
2492
  }
2409
2493
  }
2410
- .md\:justify-start {
2411
- @media (width >= 48rem) {
2412
- justify-content: flex-start;
2413
- }
2414
- }
2415
2494
  .md\:rounded-lg {
2416
2495
  @media (width >= 48rem) {
2417
2496
  border-radius: var(--radius-lg);
@@ -2571,19 +2650,14 @@
2571
2650
  width: calc(var(--spacing) * 12);
2572
2651
  }
2573
2652
  }
2574
- .xl\:sticky {
2575
- @media (width >= 80rem) {
2576
- position: sticky;
2577
- }
2578
- }
2579
- .xl\:top-4 {
2653
+ .xl\:order-last {
2580
2654
  @media (width >= 80rem) {
2581
- top: calc(var(--spacing) * 4);
2655
+ order: 9999;
2582
2656
  }
2583
2657
  }
2584
- .xl\:order-last {
2658
+ .xl\:min-h-\[calc\(100dvh-2rem\)\] {
2585
2659
  @media (width >= 80rem) {
2586
- order: 9999;
2660
+ min-height: calc(100dvh - 2rem);
2587
2661
  }
2588
2662
  }
2589
2663
  .xl\:w-72 {
@@ -2596,6 +2670,23 @@
2596
2670
  flex-direction: row;
2597
2671
  }
2598
2672
  }
2673
+ .xl\:border-b-0 {
2674
+ @media (width >= 80rem) {
2675
+ border-bottom-style: var(--tw-border-style);
2676
+ border-bottom-width: 0px;
2677
+ }
2678
+ }
2679
+ .xl\:border-l {
2680
+ @media (width >= 80rem) {
2681
+ border-left-style: var(--tw-border-style);
2682
+ border-left-width: 1px;
2683
+ }
2684
+ }
2685
+ .xl\:pl-4 {
2686
+ @media (width >= 80rem) {
2687
+ padding-left: calc(var(--spacing) * 4);
2688
+ }
2689
+ }
2599
2690
  }
2600
2691
  @font-face {
2601
2692
  font-family: 'Inter';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dcrackel/hematournamentui",
3
3
  "private": false,
4
- "version": "1.0.713",
4
+ "version": "1.0.715",
5
5
  "type": "module",
6
6
  "main": "dist/HemaTournamentUI-lib.umd.js",
7
7
  "module": "dist/HemaTournamentUI-lib.es.js",
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 LoadingModal } from './stories/Molecules/Modals/LoadingModal/LoadingModal.vue';
13
- export { default as EventWizardModal } from './stories/Molecules/Modals/EventWizardModal/EventWizardModal.vue';
14
-
15
- export { default as AddressAutocomplete } from './stories/Organisms/ComplexInputs/AddressAutocomplete/AddressAutocomplete.vue';
16
- export { default as DatePicker } from './stories/Organisms/ComplexInputs/DatePicker/DatePicker.vue';
17
- export { default as DropDownMenu } from './stories/Organisms/ComplexInputs/DropDown/DropDownMenu.vue';
18
- export { default as GridContainer } from './stories/Organisms/Grids/GridContainer.vue';
19
- export { default as PoolGrid } from './stories/Organisms/Containers/PoolGrid/PoolGrid.vue';
20
- export { default as BracketDirectorAssignmentCard } from './stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.vue';
21
- export { default as BracketDirectorAssignmentPanel } from './stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.vue';
22
- export { default as ImageCropper } from './stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.vue';
23
- export { default as TextBoxEditor } from './stories/Organisms/ComplexInputs/TextBoxEditor/TextBoxEditor.vue';
24
- export { default as EditEventWizard } from './stories/Organisms/Wizards/EditEventWizard/EditEventWizard.vue';
25
-
26
- export { default as TournamentManagement } from './stories/Templates/TournamentManagement/TournamentManagement.vue';
27
- export { default as ListEvents } from './stories/Templates/TournamentManagement/ListEvents/ListEvents.vue';
28
-
29
- export { default as AdminLeftMenu } from './stories/Templates/Menus/Admin/AdminLeftMenu.vue';
30
- export { default as EventAttendance } from './stories/Templates/EventManagement/EventAttendance/EventAttendance.vue';
31
- export { default as PoolManagement } from './stories/Templates/EventManagement/PoolManagement/PoolManagement.vue';
32
- export { default as StaffList } from './stories/Templates/EventManagement/StaffList/StaffList.vue';
33
- export { default as PoolLive } from './stories/Templates/EventManagement/PoolLive/PoolLive.vue';
34
- export { default as PoolResults } from './stories/Templates/EventManagement/PoolResults/PoolResults.vue';
35
- export { default as Bracket } from './stories/Templates/EventManagement/Bracket/Bracket.vue';
36
- export { default as EditEventInfo } from './stories/Templates/EventManagement/EditEventInfo/EditEventInfo.vue';
37
- export { default as EventFinalResults } from './stories/Templates/EventManagement/EventFinalResults/EventFinalResults.vue';
38
- export { default as KioskDisplay } from './stories/Templates/EventManagement/KioskDisplay/KioskDisplay.vue';
39
- export { default as PersonManagement } from './stories/Templates/PersonManagement/PersonManagement.vue';
40
- export { default as ClubManagement } from './stories/Templates/ClubManagement/ClubManagement.vue';
41
- export { default as Assignment } from './stories/Templates/Assignment/Assignment.vue';
42
- export { default as PenaltyReport } from './stories/Templates/EventManagement/Penalties/PenaltyReport.vue';
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
+ });