@dcrackel/hematournamentui 1.0.712 → 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 (99) hide show
  1. package/.prettierignore +7 -7
  2. package/.storybook/main.js +3 -1
  3. package/README.md +30 -0
  4. package/package.json +96 -96
  5. package/prettier.config.js +16 -16
  6. package/src/main.js +1 -1
  7. package/src/mocks/getDEBouts-TableOf128.js +37 -37
  8. package/src/mocks/getDEBouts-TableOf64.js +35 -35
  9. package/src/stories/Atoms/Input/BaseInput.test.js +97 -97
  10. package/src/stories/Atoms/Input/BaseInput.vue +149 -149
  11. package/src/stories/Molecules/Indicators/MissingHemaRatingsId/MissingHemaRatingsId.stories.js +23 -23
  12. package/src/stories/Molecules/Indicators/MissingHemaRatingsId/MissingHemaRatingsId.vue +35 -35
  13. package/src/stories/Molecules/Indicators/RegistrationStatus/RegistrationStatus.vue +30 -30
  14. package/src/stories/Molecules/Menus/HoverActions/HoverActions.test.js +138 -0
  15. package/src/stories/Molecules/Menus/HoverActions/HoverActions.vue +212 -0
  16. package/src/stories/Molecules/Modals/DirectorModal/DirectorModal.stories.js +6 -0
  17. package/src/stories/Molecules/Modals/DirectorModal/DirectorModal.test.js +18 -18
  18. package/src/stories/Molecules/Modals/DirectorModal/DirectorModal.vue +1 -1
  19. package/src/stories/Organisms/Cards/BoutCard/BoutCard.vue +5 -1
  20. package/src/stories/Organisms/Cards/BoutCard/Variants/BoutCardActive/BoutCardActive.vue +19 -10
  21. package/src/stories/Organisms/Cards/BoutCard/Variants/BoutCardInactive/BoutCardInactive.vue +11 -7
  22. package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.stories.js +33 -33
  23. package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.test.js +45 -45
  24. package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.vue +103 -103
  25. package/src/stories/Organisms/Cards/Director/DirectorCard.test.js +82 -0
  26. package/src/stories/Organisms/Cards/Director/DirectorCard.vue +32 -51
  27. package/src/stories/Organisms/Cards/EditBout/EditBout.test.js +51 -51
  28. package/src/stories/Organisms/Cards/EventCard/EventCard.test.js +106 -106
  29. package/src/stories/Organisms/Cards/FencerCard/FencerCard.vue +298 -298
  30. package/src/stories/Organisms/Cards/TableauBoutCard/TableauBoutCard.stories.js +77 -60
  31. package/src/stories/Organisms/Cards/TableauBoutCard/TableauBoutCard.test.js +144 -143
  32. package/src/stories/Organisms/Cards/TableauBoutCard/TableauBoutCard.vue +289 -256
  33. package/src/stories/Organisms/Cards/TableauFencerCard/TableauFencerCard.stories.js +69 -69
  34. package/src/stories/Organisms/Cards/TableauFencerCard/TableauFencerCard.vue +80 -80
  35. package/src/stories/Organisms/Cards/WaitlistFencerCard/WaitlistFencerCard.vue +153 -153
  36. package/src/stories/Organisms/ComplexInputs/DropDown/DropDownMenu.vue +4 -0
  37. package/src/stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.stories.js +31 -31
  38. package/src/stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.test.js +109 -109
  39. package/src/stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.vue +192 -192
  40. package/src/stories/Organisms/Containers/Tableau/Tableau.stories.js +1 -0
  41. package/src/stories/Organisms/Containers/Tableau/Tableau.test.js +247 -204
  42. package/src/stories/Organisms/Containers/Tableau/Tableau.vue +89 -15
  43. package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.test.js +29 -29
  44. package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.vue +46 -46
  45. package/src/stories/Organisms/Containers/Tableau/components/TableauSvgOverlay.test.js +21 -21
  46. package/src/stories/Organisms/Containers/Tableau/components/TableauSvgOverlay.vue +39 -39
  47. package/src/stories/Organisms/Containers/Tableau/layout/TableauLayoutEngine.js +145 -145
  48. package/src/stories/Organisms/Containers/Tableau/layout/TableauLayoutEngine.test.js +64 -64
  49. package/src/stories/Organisms/Containers/Tableau/plannedAssignments.js +36 -57
  50. package/src/stories/Organisms/Containers/Tableau/plannedAssignments.test.js +101 -65
  51. package/src/stories/Organisms/Containers/TableauColumn/TableauColumn.vue +14 -1
  52. package/src/stories/Organisms/Wizards/EditEventWizard/BasicEventInfo/BasicEventInfo.stories.js +52 -52
  53. package/src/stories/Organisms/Wizards/EditEventWizard/BasicEventInfo/BasicEventInfo.vue +10 -3
  54. package/src/stories/Organisms/Wizards/EditEventWizard/DirectElimination/DirectElimination.vue +226 -226
  55. package/src/stories/Organisms/Wizards/EditEventWizard/DirectEliminationPromotion/DirectEliminationPromotion.vue +204 -204
  56. package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.stories.js +137 -137
  57. package/src/stories/Organisms/Wizards/EditEventWizard/EventCustomization/EventCustomization.vue +172 -172
  58. package/src/stories/Organisms/Wizards/EditEventWizard/PoolConfiguration/PoolConfiguration.vue +216 -216
  59. package/src/stories/Organisms/Wizards/EditEventWizard/PriceFields.test.js +79 -79
  60. package/src/stories/Organisms/Wizards/EditEventWizard/RuleSerialization.test.js +29 -29
  61. package/src/stories/Organisms/Wizards/EditEventWizard/WeaponSelection.test.js +138 -0
  62. package/src/stories/Templates/EventManagement/Bracket/Bracket.stories.js +123 -12
  63. package/src/stories/Templates/EventManagement/Bracket/Bracket.test.js +178 -166
  64. package/src/stories/Templates/EventManagement/Bracket/Bracket.vue +35 -7
  65. package/src/stories/Templates/EventManagement/Bracket/BracketDirectorAssignment.test.js +242 -0
  66. package/src/stories/Templates/EventManagement/Bracket/BracketMobile.test.js +131 -0
  67. package/src/stories/Templates/EventManagement/Bracket/BracketPlannedSuggestions.js +42 -39
  68. package/src/stories/Templates/EventManagement/Bracket/BracketStories.test.js +80 -0
  69. package/src/stories/Templates/EventManagement/Bracket/BracketStoryAssignments.js +93 -0
  70. package/src/stories/Templates/EventManagement/Bracket/BracketStoryAssignments.test.js +152 -0
  71. package/src/stories/Templates/EventManagement/Bracket/BracketStoryHarness.js +39 -39
  72. package/src/stories/Templates/EventManagement/Bracket/BracketStoryHarness.test.js +79 -79
  73. package/src/stories/Templates/EventManagement/EditEventInfo/EditEventInfo.stories.js +93 -93
  74. package/src/stories/Templates/EventManagement/EditEventInfo/EditEventInfo.vue +91 -91
  75. package/src/stories/Templates/EventManagement/PoolLive/PoolLive.test.js +121 -121
  76. package/src/stories/Templates/Menus/EditEventsTopMenu/EditEventsTopMenu.stories.js +9 -2
  77. package/src/stories/Templates/Menus/EditEventsTopMenu/EditEventsTopMenu.vue +5 -4
  78. package/src/stories/Templates/PersonManagement/Admin/Administration/Administration.stories.js +81 -81
  79. package/src/stories/Templates/PersonManagement/Admin/Administration/Administration.test.js +39 -39
  80. package/src/stories/Templates/PersonManagement/Admin/Administration/Administration.vue +101 -101
  81. package/src/stories/Templates/PersonManagement/Admin/FencerAdmin/FencerAdmin.stories.js +71 -71
  82. package/src/stories/Templates/PersonManagement/Admin/FencerAdmin/FencerAdmin.vue +86 -86
  83. package/src/stories/Templates/PersonManagement/Admin/MergeAccounts/MergeAccounts.stories.js +86 -86
  84. package/src/stories/Templates/PersonManagement/Admin/MergeAccounts/MergeAccounts.vue +247 -247
  85. package/src/stories/Templates/PersonManagement/Basic/BasicEdit.vue +170 -170
  86. package/src/stories/Templates/PersonManagement/PersonManagement.stories.js +156 -156
  87. package/src/stories/Templates/PersonManagement/PersonManagement.test.js +329 -329
  88. package/src/stories/Templates/PersonManagement/PersonManagement.vue +329 -329
  89. package/src/stories/Templates/PersonManagement/PersonManagementDraft.js +42 -42
  90. package/src/stories/Templates/TournamentManagement/TournamentManagement.stories.js +84 -84
  91. package/src/stories/Templates/TournamentManagement/TournamentManagement.test.js +114 -114
  92. package/src/stories/Templates/TournamentManagement/TournamentManagement.vue +363 -363
  93. package/src/stories/Templates/TournamentManagement/TournamentPageOne/TournamentPageOne.stories.js +116 -116
  94. package/src/stories/Templates/TournamentManagement/TournamentPageOne/TournamentPageOne.vue +307 -307
  95. package/src/stories/Util/Validations/PersonManagementValidations.js +45 -45
  96. package/src/stories/Util/Validations/PriceValidation.js +6 -6
  97. package/src/stories/Util/boutDirectorAssignment.js +46 -0
  98. package/src/stories/Util/registrationStatus.js +11 -11
  99. 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
+ });