@dcrackel/hematournamentui 1.0.711 → 1.0.713

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 (109) hide show
  1. package/.prettierignore +7 -7
  2. package/.storybook/main.js +3 -1
  3. package/README.md +182 -148
  4. package/dist/HemaTournamentUI-lib.es.js +4150 -4039
  5. package/dist/HemaTournamentUI-lib.umd.js +27 -27
  6. package/dist/hematournamentui-tailwind.css +11 -0
  7. package/package.json +96 -96
  8. package/prettier.config.js +16 -16
  9. package/src/main.js +1 -1
  10. package/src/mocks/getDEBouts-TableOf128.js +37 -37
  11. package/src/mocks/getDEBouts-TableOf64.js +35 -35
  12. package/src/stories/Atoms/Input/BaseInput.test.js +97 -97
  13. package/src/stories/Atoms/Input/BaseInput.vue +149 -149
  14. package/src/stories/Molecules/Indicators/MissingHemaRatingsId/MissingHemaRatingsId.stories.js +23 -23
  15. package/src/stories/Molecules/Indicators/MissingHemaRatingsId/MissingHemaRatingsId.vue +35 -35
  16. package/src/stories/Molecules/Indicators/RegistrationStatus/RegistrationStatus.vue +30 -0
  17. package/src/stories/Molecules/Menus/HoverActions/HoverActions.test.js +138 -0
  18. package/src/stories/Molecules/Menus/HoverActions/HoverActions.vue +212 -0
  19. package/src/stories/Molecules/Modals/DirectorModal/DirectorModal.stories.js +6 -0
  20. package/src/stories/Molecules/Modals/DirectorModal/DirectorModal.test.js +18 -18
  21. package/src/stories/Molecules/Modals/DirectorModal/DirectorModal.vue +1 -1
  22. package/src/stories/Organisms/Cards/BoutCard/BoutCard.vue +5 -1
  23. package/src/stories/Organisms/Cards/BoutCard/Variants/BoutCardActive/BoutCardActive.vue +19 -10
  24. package/src/stories/Organisms/Cards/BoutCard/Variants/BoutCardInactive/BoutCardInactive.vue +11 -7
  25. package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.stories.js +33 -33
  26. package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.test.js +45 -45
  27. package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.vue +103 -103
  28. package/src/stories/Organisms/Cards/Director/DirectorCard.test.js +82 -0
  29. package/src/stories/Organisms/Cards/Director/DirectorCard.vue +32 -51
  30. package/src/stories/Organisms/Cards/EditBout/EditBout.test.js +51 -51
  31. package/src/stories/Organisms/Cards/EventCard/EventCard.stories.js +41 -1
  32. package/src/stories/Organisms/Cards/EventCard/EventCard.test.js +106 -0
  33. package/src/stories/Organisms/Cards/EventCard/EventCard.vue +62 -30
  34. package/src/stories/Organisms/Cards/FencerCard/FencerCard.vue +298 -298
  35. package/src/stories/Organisms/Cards/TableauBoutCard/TableauBoutCard.stories.js +77 -60
  36. package/src/stories/Organisms/Cards/TableauBoutCard/TableauBoutCard.test.js +144 -143
  37. package/src/stories/Organisms/Cards/TableauBoutCard/TableauBoutCard.vue +289 -256
  38. package/src/stories/Organisms/Cards/TableauFencerCard/TableauFencerCard.stories.js +69 -69
  39. package/src/stories/Organisms/Cards/TableauFencerCard/TableauFencerCard.vue +80 -80
  40. package/src/stories/Organisms/Cards/WaitlistFencerCard/WaitlistFencerCard.vue +153 -153
  41. package/src/stories/Organisms/ComplexInputs/DropDown/DropDownMenu.vue +4 -0
  42. package/src/stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.stories.js +31 -31
  43. package/src/stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.test.js +109 -109
  44. package/src/stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.vue +192 -192
  45. package/src/stories/Organisms/Containers/Tableau/Tableau.stories.js +1 -0
  46. package/src/stories/Organisms/Containers/Tableau/Tableau.test.js +247 -204
  47. package/src/stories/Organisms/Containers/Tableau/Tableau.vue +89 -15
  48. package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.test.js +29 -29
  49. package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.vue +46 -46
  50. package/src/stories/Organisms/Containers/Tableau/components/TableauSvgOverlay.test.js +21 -21
  51. package/src/stories/Organisms/Containers/Tableau/components/TableauSvgOverlay.vue +39 -39
  52. package/src/stories/Organisms/Containers/Tableau/layout/TableauLayoutEngine.js +145 -145
  53. package/src/stories/Organisms/Containers/Tableau/layout/TableauLayoutEngine.test.js +64 -64
  54. package/src/stories/Organisms/Containers/Tableau/plannedAssignments.js +36 -57
  55. package/src/stories/Organisms/Containers/Tableau/plannedAssignments.test.js +101 -65
  56. package/src/stories/Organisms/Containers/TableauColumn/TableauColumn.vue +14 -1
  57. package/src/stories/Organisms/Headers/AttendanceSummary/AttendanceSummary.vue +12 -5
  58. package/src/stories/Organisms/Wizards/EditEventWizard/BasicEventInfo/BasicEventInfo.stories.js +52 -48
  59. package/src/stories/Organisms/Wizards/EditEventWizard/BasicEventInfo/BasicEventInfo.vue +256 -227
  60. package/src/stories/Organisms/Wizards/EditEventWizard/DirectElimination/DirectElimination.vue +226 -226
  61. package/src/stories/Organisms/Wizards/EditEventWizard/DirectEliminationPromotion/DirectEliminationPromotion.vue +204 -204
  62. package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.stories.js +137 -133
  63. package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.test.js +221 -191
  64. package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.vue +372 -366
  65. package/src/stories/Organisms/Wizards/EditEventWizard/EventCustomization/EventCustomization.vue +172 -172
  66. package/src/stories/Organisms/Wizards/EditEventWizard/PoolConfiguration/PoolConfiguration.vue +216 -216
  67. package/src/stories/Organisms/Wizards/EditEventWizard/PriceFields.test.js +79 -79
  68. package/src/stories/Organisms/Wizards/EditEventWizard/RuleSerialization.test.js +29 -0
  69. package/src/stories/Organisms/Wizards/EditEventWizard/WeaponSelection.test.js +138 -0
  70. package/src/stories/Templates/EventManagement/Bracket/Bracket.stories.js +123 -12
  71. package/src/stories/Templates/EventManagement/Bracket/Bracket.test.js +178 -166
  72. package/src/stories/Templates/EventManagement/Bracket/Bracket.vue +35 -7
  73. package/src/stories/Templates/EventManagement/Bracket/BracketDirectorAssignment.test.js +242 -0
  74. package/src/stories/Templates/EventManagement/Bracket/BracketMobile.test.js +131 -0
  75. package/src/stories/Templates/EventManagement/Bracket/BracketPlannedSuggestions.js +42 -39
  76. package/src/stories/Templates/EventManagement/Bracket/BracketStories.test.js +80 -0
  77. package/src/stories/Templates/EventManagement/Bracket/BracketStoryAssignments.js +93 -0
  78. package/src/stories/Templates/EventManagement/Bracket/BracketStoryAssignments.test.js +152 -0
  79. package/src/stories/Templates/EventManagement/Bracket/BracketStoryHarness.js +39 -39
  80. package/src/stories/Templates/EventManagement/Bracket/BracketStoryHarness.test.js +79 -79
  81. package/src/stories/Templates/EventManagement/EditEventInfo/EditEventInfo.stories.js +93 -93
  82. package/src/stories/Templates/EventManagement/EditEventInfo/EditEventInfo.vue +91 -91
  83. package/src/stories/Templates/EventManagement/EventAttendance/EventAttendance.stories.js +31 -1
  84. package/src/stories/Templates/EventManagement/PoolLive/PoolLive.test.js +121 -121
  85. package/src/stories/Templates/Menus/EditEventsTopMenu/EditEventsTopMenu.stories.js +9 -2
  86. package/src/stories/Templates/Menus/EditEventsTopMenu/EditEventsTopMenu.vue +5 -4
  87. package/src/stories/Templates/PersonManagement/Admin/Administration/Administration.stories.js +81 -81
  88. package/src/stories/Templates/PersonManagement/Admin/Administration/Administration.test.js +39 -39
  89. package/src/stories/Templates/PersonManagement/Admin/Administration/Administration.vue +101 -101
  90. package/src/stories/Templates/PersonManagement/Admin/FencerAdmin/FencerAdmin.stories.js +71 -71
  91. package/src/stories/Templates/PersonManagement/Admin/FencerAdmin/FencerAdmin.vue +86 -86
  92. package/src/stories/Templates/PersonManagement/Admin/MergeAccounts/MergeAccounts.stories.js +86 -86
  93. package/src/stories/Templates/PersonManagement/Admin/MergeAccounts/MergeAccounts.vue +247 -247
  94. package/src/stories/Templates/PersonManagement/Basic/BasicEdit.vue +170 -170
  95. package/src/stories/Templates/PersonManagement/PersonManagement.stories.js +156 -156
  96. package/src/stories/Templates/PersonManagement/PersonManagement.test.js +329 -329
  97. package/src/stories/Templates/PersonManagement/PersonManagement.vue +329 -329
  98. package/src/stories/Templates/PersonManagement/PersonManagementDraft.js +42 -42
  99. package/src/stories/Templates/TournamentManagement/ListEvents/ListEvents.stories.js +58 -45
  100. package/src/stories/Templates/TournamentManagement/TournamentManagement.stories.js +84 -84
  101. package/src/stories/Templates/TournamentManagement/TournamentManagement.test.js +114 -114
  102. package/src/stories/Templates/TournamentManagement/TournamentManagement.vue +363 -363
  103. package/src/stories/Templates/TournamentManagement/TournamentPageOne/TournamentPageOne.stories.js +116 -116
  104. package/src/stories/Templates/TournamentManagement/TournamentPageOne/TournamentPageOne.vue +307 -307
  105. package/src/stories/Util/Validations/PersonManagementValidations.js +45 -45
  106. package/src/stories/Util/Validations/PriceValidation.js +6 -6
  107. package/src/stories/Util/boutDirectorAssignment.js +46 -0
  108. package/src/stories/Util/registrationStatus.js +11 -0
  109. package/src/stories/Util/tabs.js +19 -47
@@ -1,103 +1,103 @@
1
- <template>
2
- <button
3
- v-if="isAddCard"
4
- type="button"
5
- data-testid="add-bracket-director"
6
- class="flex min-h-20 w-full items-center justify-center gap-3 rounded-lg border-2 border-dashed border-dropdownSelect bg-white px-4 py-3 text-left transition hover:border-bright hover:shadow-md focus-visible:border-bright focus-visible:outline-none"
7
- @click="$emit('action:add')"
8
- >
9
- <span class="flex h-9 w-9 items-center justify-center rounded-full bg-bannerBackground" aria-hidden="true">
10
- <BaseIcon iconName="fa-plus" color="primaryHighlight" size="sm" />
11
- </span>
12
- <BaseText :text="addLabel" color="primaryHighlight" size="sm" weight="bold" />
13
- </button>
14
-
15
- <article
16
- v-else-if="station"
17
- data-testid="bracket-director-assignment"
18
- class="flex min-h-16 w-full items-center gap-3 overflow-hidden rounded-md border border-poolSetup bg-white px-3 py-2 shadow-sm hover:border-bright"
19
- :aria-label="assignmentLabel"
20
- >
21
- <div class="relative h-12 w-12 flex-none">
22
- <img
23
- v-if="portraitUrl"
24
- :src="portraitUrl"
25
- :alt="`${directorName} portrait`"
26
- class="h-12 w-12 rounded-xl object-cover"
27
- />
28
- <div
29
- v-else
30
- data-testid="director-initials"
31
- class="flex h-12 w-12 items-center justify-center rounded-xl bg-dropdownSelect"
32
- aria-hidden="true"
33
- >
34
- <BaseText :text="directorInitials" color="primaryHighlight" size="sm" weight="bold" />
35
- </div>
36
- <span
37
- data-testid="director-color-dot"
38
- class="absolute -bottom-1 -right-1 h-4 w-4 rounded-full ring-2 ring-white"
39
- :style="{ backgroundColor: station.Color }"
40
- aria-hidden="true"
41
- ></span>
42
- </div>
43
-
44
- <div class="min-w-0 flex-1">
45
- <BaseText :text="ringLabel" color="quaternary" size="xs" />
46
- <BaseText class="truncate" :text="directorName" color="primaryHighlight" size="sm" weight="bold" />
47
- </div>
48
- </article>
49
- </template>
50
-
51
- <script>
52
- import BaseIcon from '../../../Atoms/Icon/BaseIcon.vue';
53
- import BaseText from '../../../Atoms/Text/BaseText.vue';
54
-
55
- const getInitials = displayName =>
56
- String(displayName || '')
57
- .split(/\s+/)
58
- .filter(Boolean)
59
- .slice(0, 2)
60
- .map(name => name[0].toUpperCase())
61
- .join('');
62
-
63
- export default {
64
- name: 'BracketDirectorAssignmentCard',
65
- components: { BaseIcon, BaseText },
66
- emits: ['action:add'],
67
- props: {
68
- station: {
69
- type: Object,
70
- default: null
71
- },
72
- variant: {
73
- type: String,
74
- default: 'assigned',
75
- validator: value => ['assigned', 'add'].includes(value)
76
- },
77
- addLabel: {
78
- type: String,
79
- default: 'Click here to add director'
80
- }
81
- },
82
- computed: {
83
- assignmentLabel() {
84
- return `${this.ringLabel}, Director ${this.directorName}`;
85
- },
86
- directorInitials() {
87
- return getInitials(this.directorName) || '?';
88
- },
89
- directorName() {
90
- return this.station?.Director?.DisplayName || 'Director TBD';
91
- },
92
- isAddCard() {
93
- return this.variant === 'add';
94
- },
95
- portraitUrl() {
96
- return this.station?.Director?.PortraitUrl || '';
97
- },
98
- ringLabel() {
99
- return this.station?.RingName || `Ring ${this.station?.RingId ?? ''}`.trim();
100
- }
101
- }
102
- };
103
- </script>
1
+ <template>
2
+ <button
3
+ v-if="isAddCard"
4
+ type="button"
5
+ data-testid="add-bracket-director"
6
+ class="flex min-h-20 w-full items-center justify-center gap-3 rounded-lg border-2 border-dashed border-dropdownSelect bg-white px-4 py-3 text-left transition hover:border-bright hover:shadow-md focus-visible:border-bright focus-visible:outline-none"
7
+ @click="$emit('action:add')"
8
+ >
9
+ <span class="flex h-9 w-9 items-center justify-center rounded-full bg-bannerBackground" aria-hidden="true">
10
+ <BaseIcon iconName="fa-plus" color="primaryHighlight" size="sm" />
11
+ </span>
12
+ <BaseText :text="addLabel" color="primaryHighlight" size="sm" weight="bold" />
13
+ </button>
14
+
15
+ <article
16
+ v-else-if="station"
17
+ data-testid="bracket-director-assignment"
18
+ class="flex min-h-16 w-full items-center gap-3 overflow-hidden rounded-md border border-poolSetup bg-white px-3 py-2 shadow-sm hover:border-bright"
19
+ :aria-label="assignmentLabel"
20
+ >
21
+ <div class="relative h-12 w-12 flex-none">
22
+ <img
23
+ v-if="portraitUrl"
24
+ :src="portraitUrl"
25
+ :alt="`${directorName} portrait`"
26
+ class="h-12 w-12 rounded-xl object-cover"
27
+ />
28
+ <div
29
+ v-else
30
+ data-testid="director-initials"
31
+ class="flex h-12 w-12 items-center justify-center rounded-xl bg-dropdownSelect"
32
+ aria-hidden="true"
33
+ >
34
+ <BaseText :text="directorInitials" color="primaryHighlight" size="sm" weight="bold" />
35
+ </div>
36
+ <span
37
+ data-testid="director-color-dot"
38
+ class="absolute -bottom-1 -right-1 h-4 w-4 rounded-full ring-2 ring-white"
39
+ :style="{ backgroundColor: station.Color }"
40
+ aria-hidden="true"
41
+ ></span>
42
+ </div>
43
+
44
+ <div class="min-w-0 flex-1">
45
+ <BaseText :text="ringLabel" color="quaternary" size="xs" />
46
+ <BaseText class="truncate" :text="directorName" color="primaryHighlight" size="sm" weight="bold" />
47
+ </div>
48
+ </article>
49
+ </template>
50
+
51
+ <script>
52
+ import BaseIcon from '../../../Atoms/Icon/BaseIcon.vue';
53
+ import BaseText from '../../../Atoms/Text/BaseText.vue';
54
+
55
+ const getInitials = displayName =>
56
+ String(displayName || '')
57
+ .split(/\s+/)
58
+ .filter(Boolean)
59
+ .slice(0, 2)
60
+ .map(name => name[0].toUpperCase())
61
+ .join('');
62
+
63
+ export default {
64
+ name: 'BracketDirectorAssignmentCard',
65
+ components: { BaseIcon, BaseText },
66
+ emits: ['action:add'],
67
+ props: {
68
+ station: {
69
+ type: Object,
70
+ default: null
71
+ },
72
+ variant: {
73
+ type: String,
74
+ default: 'assigned',
75
+ validator: value => ['assigned', 'add'].includes(value)
76
+ },
77
+ addLabel: {
78
+ type: String,
79
+ default: 'Click here to add director'
80
+ }
81
+ },
82
+ computed: {
83
+ assignmentLabel() {
84
+ return `${this.ringLabel}, Director ${this.directorName}`;
85
+ },
86
+ directorInitials() {
87
+ return getInitials(this.directorName) || '?';
88
+ },
89
+ directorName() {
90
+ return this.station?.Director?.DisplayName || 'Director TBD';
91
+ },
92
+ isAddCard() {
93
+ return this.variant === 'add';
94
+ },
95
+ portraitUrl() {
96
+ return this.station?.Director?.PortraitUrl || '';
97
+ },
98
+ ringLabel() {
99
+ return this.station?.RingName || `Ring ${this.station?.RingId ?? ''}`.trim();
100
+ }
101
+ }
102
+ };
103
+ </script>
@@ -0,0 +1,82 @@
1
+ import { mount, shallowMount } from '@vue/test-utils';
2
+ import { describe, expect, it } from 'vitest';
3
+ import DirectorCard from './DirectorCard.vue';
4
+ import DirectorModal from '../../../Molecules/Modals/DirectorModal/DirectorModal.vue';
5
+ import ScoreBox from '../../../Molecules/Boxes/BoutBoxes/ScoreBox/ScoreBox.vue';
6
+ import eventRules from '../../../../mocks/eventRules.js';
7
+ import getPoolsWithBouts from '../../../../mocks/getPoolsWithBouts.js';
8
+
9
+ const createProps = rules => ({
10
+ bout: structuredClone(getPoolsWithBouts[0]),
11
+ eventRules: rules,
12
+ hostingClubColors: { Color1: 'red', Color2: 'blue' }
13
+ });
14
+
15
+ const poolRules = [
16
+ { RuleName: 'PoolTime', RuleValue: '120' },
17
+ { RuleName: 'PoolMaxPoints', RuleValue: '5' },
18
+ { RuleName: 'PoolNumOfPasses', RuleValue: '4' },
19
+ { RuleName: 'PoolScoreDirection', RuleValue: 'false' },
20
+ { RuleName: 'CountHandshots', RuleValue: 'true' },
21
+ { RuleName: 'PenaltiesReset', RuleValue: 'true' }
22
+ ];
23
+
24
+ describe('DirectorCard event rules', () => {
25
+ it.each([
26
+ ['flat', poolRules],
27
+ ['nested', poolRules.map(({ RuleName, RuleValue }) => ({ Rules: { RuleName }, RuleValue }))],
28
+ ['mixed with invalid entries', [null, undefined, {}, { Rules: null }, ...poolRules]]
29
+ ])('applies %s rules without crashing', (name, rules) => {
30
+ const props = createProps(rules);
31
+ const originalBout = structuredClone(props.bout);
32
+ const wrapper = shallowMount(DirectorCard, { props });
33
+ expect(wrapper.vm.timeLeft).toBe(120);
34
+ expect(wrapper.vm.maxPoints).toBe(5);
35
+ expect(wrapper.vm.maxPasses).toBe(4);
36
+ expect(wrapper.vm.showPassesBox).toBe(true);
37
+ expect(wrapper.vm.showTimerBox).toBe(true);
38
+ expect(wrapper.vm.showHandShotCounterBox).toBe(true);
39
+ expect(wrapper.vm.penaltiesReset).toBe(true);
40
+ expect(wrapper.findComponent(ScoreBox).props('isCountingUp')).toBe(false);
41
+ expect(wrapper.emitted('update:bout')[0][0]).toMatchObject({
42
+ Status: 'Scheduled',
43
+ TimeLeft: 120,
44
+ IsCountingUp: false,
45
+ MaxPoints: 5
46
+ });
47
+ expect(props.bout).toEqual(originalBout);
48
+ wrapper.unmount();
49
+ });
50
+
51
+ it.each([undefined, null, [], [null, undefined, {}, { Rules: {} }]])(
52
+ 'uses defaults for missing or invalid rules: %j',
53
+ rules => {
54
+ const wrapper = shallowMount(DirectorCard, { props: createProps(rules) });
55
+ expect(wrapper.vm.timeLeft).toBe(180);
56
+ expect(wrapper.vm.maxPoints).toBe(7);
57
+ expect(wrapper.vm.maxPasses).toBe(7);
58
+ expect(wrapper.vm.isCountingUp).toBe(true);
59
+ expect(wrapper.vm.showTimerBox).toBe(false);
60
+ expect(wrapper.vm.showPassesBox).toBe(false);
61
+ expect(wrapper.vm.showHandShotCounterBox).toBe(false);
62
+ expect(wrapper.vm.penaltiesReset).toBe(false);
63
+ wrapper.unmount();
64
+ }
65
+ );
66
+
67
+ it('opens the real modal with Storybook rules and preserves scores on close', async () => {
68
+ const props = createProps(eventRules);
69
+ const wrapper = mount(DirectorModal, { props: { ...props, show: false } });
70
+ await wrapper.setProps({ show: true });
71
+ expect(wrapper.findComponent(DirectorCard).exists()).toBe(true);
72
+ wrapper.findComponent(ScoreBox).vm.$emit('update:Score', 3);
73
+ await wrapper.vm.$nextTick();
74
+ expect(wrapper.findComponent(DirectorCard).vm.localBout.Score1).toBe(3);
75
+ expect(wrapper.emitted('update:bout').at(-1)[0]).toMatchObject({ Score1: 3, Status: 'Scheduled' });
76
+ await wrapper.get('[data-testid="director-modal-backdrop"]').trigger('click');
77
+ expect(wrapper.emitted('update:closeModal')).toEqual([[props.bout]]);
78
+ expect(wrapper.findComponent(DirectorCard).vm.localBout.Score1).toBe(3);
79
+ expect(props.bout.Score1).toBe(0);
80
+ wrapper.unmount();
81
+ });
82
+ });
@@ -36,6 +36,14 @@
36
36
  @update:HandShotValue="updateHandShot2"
37
37
  />
38
38
 
39
+ <BaseText
40
+ v-if="bout?.RefName"
41
+ :text="bout.RefName"
42
+ size="sm"
43
+ color="quaternary"
44
+ class="mt-3 text-center wrap-break-word"
45
+ />
46
+
39
47
  <div class="w-11/12 mt-5 mx-6">
40
48
  <BaseButton label="Submit" type="grayAndBlue" :selected="showSubmit" @buttonClick="submitBout" />
41
49
  </div>
@@ -59,6 +67,7 @@ import PassesBox from '../../../Molecules/Boxes/BoutBoxes/PassesBox/PassesBox.vu
59
67
  import BaseButton from '../../../Molecules/Buttons/BaseButton/BaseButton.vue';
60
68
  import PenaltyConfirmationBox from '../../../Molecules/Boxes/BoutBoxes/PenaltyConfirmationBox/PenaltyConfirmationBox.vue';
61
69
  import PenaltyModal from '../../../Molecules/Modals/PenaltyModal/PenaltyModal.vue';
70
+ import BaseText from '../../../Atoms/Text/BaseText.vue';
62
71
 
63
72
  const scoreReachedByDirection = {
64
73
  true: (bout, maxPoints) => bout.Score1 === maxPoints || bout.Score2 === maxPoints,
@@ -102,7 +111,7 @@ const cloneBout = bout => ({
102
111
 
103
112
  export default {
104
113
  name: 'DirectorCard',
105
- components: { PenaltyModal, PenaltyConfirmationBox, ScoreBox, TimerBox, PassesBox, BaseButton },
114
+ components: { PenaltyModal, PenaltyConfirmationBox, ScoreBox, TimerBox, PassesBox, BaseButton, BaseText },
106
115
  emits: ['penalty:remove', 'penalty:add', 'penalty:update', 'submit:bout', 'update:bout'],
107
116
  props: {
108
117
  eventRules: Array,
@@ -116,7 +125,6 @@ export default {
116
125
  showPenaltyConfirmationBox: false,
117
126
  fencer1ForModal: true,
118
127
  localBout: cloneBout(this.bout),
119
- isCountingUp: true,
120
128
  lastNote1: '',
121
129
  lastNote2: '',
122
130
  timeLeft: 180,
@@ -126,7 +134,6 @@ export default {
126
134
  },
127
135
  mounted() {
128
136
  this.initTime();
129
- this.determineCountingDirection();
130
137
  this.emitBout();
131
138
  },
132
139
  beforeUnmount() {
@@ -136,77 +143,51 @@ export default {
136
143
  RingName() {
137
144
  return this.localBout.Pool?.RingName || 'Ring';
138
145
  },
146
+ isCountingUp() {
147
+ const rule = this.findEventRule('PoolScoreDirection');
148
+ return rule ? rule.RuleValue === 'true' : true;
149
+ },
139
150
  showPassesBox() {
140
151
  return (
141
- Array.isArray(this.eventRules) &&
142
152
  !this.showPenaltyConfirmationBox &&
143
- (this.eventRules.some(n2 => n2.Rules.RuleName === 'PoolNumOfPasses') ||
144
- this.eventRules.some(n2 => n2.Rules.RuleName === 'DENumOfPasses'))
153
+ Boolean(this.findEventRule('PoolNumOfPasses') || this.findEventRule('DENumOfPasses'))
145
154
  );
146
155
  },
147
156
  showTimerBox() {
148
157
  return (
149
- Array.isArray(this.eventRules) &&
150
- !this.showPenaltyConfirmationBox &&
151
- (this.eventRules.some(n2 => n2.Rules.RuleName === 'PoolTime') ||
152
- this.eventRules.some(n2 => n2.Rules.RuleName === 'DETime'))
158
+ !this.showPenaltyConfirmationBox && Boolean(this.findEventRule('PoolTime') || this.findEventRule('DETime'))
153
159
  );
154
160
  },
155
161
  showHandShotCounterBox() {
156
- return (
157
- Array.isArray(this.eventRules) &&
158
- this.eventRules.some(n2 => n2.Rules.RuleName === 'CountHandshots' && n2.RuleValue === 'true')
159
- );
162
+ return this.findEventRule('CountHandshots')?.RuleValue === 'true';
160
163
  },
161
164
  penaltiesReset() {
162
- return (
163
- Array.isArray(this.eventRules) &&
164
- this.eventRules.some(n2 => n2.Rules.RuleName === 'PenaltiesReset' && n2.RuleValue === 'true')
165
- );
165
+ return this.findEventRule('PenaltiesReset')?.RuleValue === 'true';
166
166
  },
167
167
  maxPoints() {
168
- if (this.localBout.DEBoutId) {
169
- const e = Array.isArray(this.eventRules) ? this.eventRules.find(t => t.Rules.RuleName === 'DEMaxPoints') : null;
170
- return e ? parseInt(e.RuleValue) : 7;
171
- }
172
-
173
- const n2 = Array.isArray(this.eventRules)
174
- ? this.eventRules.find(e => e.Rules.RuleName === 'PoolMaxPoints')
175
- : null;
176
- return n2 ? parseInt(n2.RuleValue) : 7;
168
+ const ruleName = this.localBout.DEBoutId ? 'DEMaxPoints' : 'PoolMaxPoints';
169
+ const rule = this.findEventRule(ruleName);
170
+ return rule ? parseInt(rule.RuleValue) : 7;
177
171
  },
178
172
  maxPasses() {
179
- if (this.localBout.DEBoutId) {
180
- const e = Array.isArray(this.eventRules) ? this.eventRules.find(t => t.Rules.RuleName === 'DEMaxPasses') : null;
181
- return e ? parseInt(e.RuleValue) : 7;
182
- }
183
-
184
- const n2 = Array.isArray(this.eventRules)
185
- ? this.eventRules.find(e => e.Rules.RuleName === 'PoolNumOfPasses')
186
- : null;
187
- return n2 ? parseInt(n2.RuleValue) : 7;
173
+ const ruleName = this.localBout.DEBoutId ? 'DEMaxPasses' : 'PoolNumOfPasses';
174
+ const rule = this.findEventRule(ruleName);
175
+ return rule ? parseInt(rule.RuleValue) : 7;
188
176
  }
189
177
  },
190
178
  methods: {
179
+ findEventRule(ruleName) {
180
+ if (!Array.isArray(this.eventRules)) return undefined;
181
+ return this.eventRules.find(rule => (rule?.Rules?.RuleName ?? rule?.RuleName) === ruleName);
182
+ },
191
183
  initTime() {
192
184
  const saved = Number(this.bout?.TimeLeft);
193
- // use saved if > 0, otherwise default from rules
194
- this.timeLeft = Number.isFinite(saved) && saved > 0 ? saved : this.startTime(); // your DE/Pool rule lookup
185
+ this.timeLeft = Number.isFinite(saved) && saved > 0 ? saved : this.startTime();
195
186
  },
196
187
  startTime() {
197
- if (this.localBout.DEBoutId) {
198
- const e = Array.isArray(this.eventRules) ? this.eventRules.find(t => t.Rules.RuleName === 'DETime') : null;
199
- return e ? parseInt(e.RuleValue) : 180;
200
- }
201
-
202
- const n2 = Array.isArray(this.eventRules) ? this.eventRules.find(e => e.Rules.RuleName === 'PoolTime') : null;
203
- return n2 ? parseInt(n2.RuleValue) : 180;
204
- },
205
- determineCountingDirection() {
206
- if (Array.isArray(this.eventRules)) {
207
- const rule = this.eventRules.find(rule => rule.Rules.RuleName === 'PoolScoreDirection');
208
- this.isCountingUp = rule ? rule.RuleValue === 'true' : true;
209
- }
188
+ const ruleName = this.localBout.DEBoutId ? 'DETime' : 'PoolTime';
189
+ const rule = this.findEventRule(ruleName);
190
+ return rule ? parseInt(rule.RuleValue) : 180;
210
191
  },
211
192
  toggleTimer() {
212
193
  if (this.timerRunning) {
@@ -1,51 +1,51 @@
1
- import { shallowMount } from '@vue/test-utils';
2
- import { describe, expect, it } from 'vitest';
3
- import ToggleSwitch from '../../../Molecules/Inputs/Toggle/Toggle.vue';
4
- import EditBout from './EditBout.vue';
5
-
6
- const createBout = () => ({
7
- BoutId: 42,
8
- DEBoutId: null,
9
- Status: 'Completed',
10
- Score1: 4,
11
- Score2: 3,
12
- Person1: { PersonId: 1, DisplayName: 'Fencer One' },
13
- Person2: { PersonId: 2, DisplayName: 'Fencer Two' }
14
- });
15
-
16
- const mountEditBout = bout =>
17
- shallowMount(EditBout, {
18
- props: {
19
- bout,
20
- hostingClubColors: {},
21
- eventRules: [{ Rules: { RuleName: 'PoolMaxPoints' }, RuleValue: '7' }]
22
- }
23
- });
24
-
25
- describe('EditBout intent', () => {
26
- it('submits edited values without changing lifecycle fields', () => {
27
- const bout = createBout();
28
- const wrapper = mountEditBout(bout);
29
-
30
- wrapper.vm.plus(true);
31
- wrapper.vm.submitBout();
32
-
33
- const submittedBout = wrapper.emitted('submit:bout')[0][0];
34
- expect(submittedBout).toMatchObject({ Status: 'Completed', Score1: 5, Score2: 3 });
35
- expect(submittedBout.Ended).toBeUndefined();
36
- expect(bout.Score1).toBe(4);
37
- });
38
-
39
- it('emits make-active intent without setting Status', async () => {
40
- const bout = createBout();
41
- const wrapper = mountEditBout(bout);
42
-
43
- wrapper.findComponent(ToggleSwitch).vm.$emit('update:checked', true);
44
- await wrapper.vm.$nextTick();
45
- wrapper.vm.submitBout();
46
-
47
- expect(wrapper.emitted('submit:bout')).toBeUndefined();
48
- expect(wrapper.emitted('request:bout-active')).toEqual([[bout]]);
49
- expect(bout.Status).toBe('Completed');
50
- });
51
- });
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import { describe, expect, it } from 'vitest';
3
+ import ToggleSwitch from '../../../Molecules/Inputs/Toggle/Toggle.vue';
4
+ import EditBout from './EditBout.vue';
5
+
6
+ const createBout = () => ({
7
+ BoutId: 42,
8
+ DEBoutId: null,
9
+ Status: 'Completed',
10
+ Score1: 4,
11
+ Score2: 3,
12
+ Person1: { PersonId: 1, DisplayName: 'Fencer One' },
13
+ Person2: { PersonId: 2, DisplayName: 'Fencer Two' }
14
+ });
15
+
16
+ const mountEditBout = bout =>
17
+ shallowMount(EditBout, {
18
+ props: {
19
+ bout,
20
+ hostingClubColors: {},
21
+ eventRules: [{ Rules: { RuleName: 'PoolMaxPoints' }, RuleValue: '7' }]
22
+ }
23
+ });
24
+
25
+ describe('EditBout intent', () => {
26
+ it('submits edited values without changing lifecycle fields', () => {
27
+ const bout = createBout();
28
+ const wrapper = mountEditBout(bout);
29
+
30
+ wrapper.vm.plus(true);
31
+ wrapper.vm.submitBout();
32
+
33
+ const submittedBout = wrapper.emitted('submit:bout')[0][0];
34
+ expect(submittedBout).toMatchObject({ Status: 'Completed', Score1: 5, Score2: 3 });
35
+ expect(submittedBout.Ended).toBeUndefined();
36
+ expect(bout.Score1).toBe(4);
37
+ });
38
+
39
+ it('emits make-active intent without setting Status', async () => {
40
+ const bout = createBout();
41
+ const wrapper = mountEditBout(bout);
42
+
43
+ wrapper.findComponent(ToggleSwitch).vm.$emit('update:checked', true);
44
+ await wrapper.vm.$nextTick();
45
+ wrapper.vm.submitBout();
46
+
47
+ expect(wrapper.emitted('submit:bout')).toBeUndefined();
48
+ expect(wrapper.emitted('request:bout-active')).toEqual([[bout]]);
49
+ expect(bout.Status).toBe('Completed');
50
+ });
51
+ });
@@ -5,6 +5,14 @@ export default {
5
5
  title: 'Organisms/Cards/EventCard',
6
6
  component: EventCard,
7
7
  tags: ['autodocs'],
8
+ parameters: {
9
+ docs: {
10
+ description: {
11
+ component:
12
+ 'RegistrationStatus accepts open, waitlist, full, or closed from the consuming app. The icon tray displays a status icon and the current state using existing theme colors. These values are also supported in Status when RegistrationStatus is absent. Missing or unknown values hide the status indicator. Lifecycle Status values such as live and completed continue to control existing behavior.'
13
+ }
14
+ }
15
+ },
8
16
  args: {
9
17
  event: singleEventMock
10
18
  },
@@ -12,5 +20,37 @@ export default {
12
20
  };
13
21
 
14
22
  export const Default = {
15
- event: singleEventMock
23
+ args: { event: singleEventMock }
24
+ };
25
+
26
+ export const Open = {
27
+ args: { event: { ...singleEventMock, Status: 'registration', RegistrationStatus: 'open' } }
28
+ };
29
+
30
+ export const Waitlist = {
31
+ args: { event: { ...singleEventMock, Status: 'registration', RegistrationStatus: 'waitlist' } }
32
+ };
33
+
34
+ export const Full = {
35
+ args: { event: { ...singleEventMock, Status: 'registration', RegistrationStatus: 'full' } }
36
+ };
37
+
38
+ export const Closed = {
39
+ args: { event: { ...singleEventMock, RegistrationStatus: 'closed' } }
40
+ };
41
+
42
+ export const MissingSettings = {
43
+ args: { event: { ...singleEventMock, EventRules: [] } }
44
+ };
45
+
46
+ export const InvalidSavedLimit = {
47
+ args: {
48
+ event: {
49
+ ...singleEventMock,
50
+ EventRules: [
51
+ ...singleEventMock.EventRules.filter(rule => rule.RuleId !== 14),
52
+ { RuleId: 14, RuleValue: 'function link() { [native code] }', Rules: { RuleName: 'CutOffNumber' } }
53
+ ]
54
+ }
55
+ }
16
56
  };