@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
@@ -1,178 +1,244 @@
1
- import { shallowMount } from '@vue/test-utils';
2
- import { describe, expect, it } from 'vitest';
3
- import DirectorModal from '../../../Molecules/Modals/DirectorModal/DirectorModal.vue';
4
- import EditBoutModal from '../../../Molecules/Modals/EditBoutModal/EditBoutModal.vue';
5
- import Tableau from '../../../Organisms/Containers/Tableau/Tableau.vue';
6
- import BracketDirectorAssignmentPanel from '../../../Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.vue';
7
- import Bracket from './Bracket.vue';
8
-
9
- const createBout = (overrides = {}) => ({
10
- BoutId: 42,
11
- DEBoutId: 1,
12
- RoundLabel: 'Final',
13
- Status: 'Scheduled',
14
- Score1: 4,
15
- Score2: 3,
16
- Person1: { PersonId: 1, DisplayName: 'Fencer One' },
17
- Person2: { PersonId: 2, DisplayName: 'Fencer Two' },
18
- ...overrides
19
- });
20
-
21
- const mountBracket = (bouts = [createBout()], optionalProps = {}) =>
22
- shallowMount(Bracket, {
23
- props: {
24
- bouts,
25
- eventRules: [],
26
- hostingClubColors: {},
27
- connectedToServer: true,
28
- ...optionalProps
29
- }
30
- });
31
-
32
- describe('Bracket bout interaction', () => {
33
- it('emits directing intent for a scheduled bout and waits for Active props', async () => {
34
- const bout = createBout();
35
- const wrapper = mountBracket([bout]);
36
-
37
- wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
38
- await wrapper.vm.$nextTick();
39
-
40
- expect(wrapper.emitted('update:directing-bout')).toEqual([[bout]]);
41
- expect(wrapper.findComponent(DirectorModal).props('show')).toBe(false);
42
-
43
- await wrapper.setProps({ bouts: [{ ...bout, Status: 'Active' }] });
44
-
45
- expect(wrapper.findComponent(DirectorModal).props('show')).toBe(true);
46
- expect(wrapper.findComponent(DirectorModal).props('bout').Status).toBe('Active');
47
- });
48
-
49
- it('resumes an active bout through the directing path', async () => {
50
- const bout = createBout({ Status: 'Active' });
51
- const wrapper = mountBracket([bout]);
52
-
53
- wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
54
- await wrapper.vm.$nextTick();
55
-
56
- expect(wrapper.emitted('update:directing-bout')).toEqual([[bout]]);
57
- expect(wrapper.findComponent(DirectorModal).props('show')).toBe(true);
58
- });
59
-
60
- it('opens completed bouts for editing instead of directing', async () => {
61
- const bout = createBout({ Status: 'Completed' });
62
- const wrapper = mountBracket([bout]);
63
-
64
- wrapper.findComponent(Tableau).vm.$emit('action:editBout', bout);
65
- await wrapper.vm.$nextTick();
66
-
67
- expect(wrapper.findComponent(EditBoutModal).props('show')).toBe(true);
68
- expect(wrapper.findComponent(DirectorModal).props('show')).toBe(false);
69
- expect(wrapper.emitted('update:directing-bout')).toBeUndefined();
70
- });
71
-
72
- it('does not direct a BYE bout', async () => {
73
- const bout = createBout({
74
- Person2: { PersonId: 0, DisplayName: 'BYE' }
75
- });
76
- const wrapper = mountBracket([bout]);
77
-
78
- wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
79
- await wrapper.vm.$nextTick();
80
-
81
- expect(wrapper.emitted('update:directing-bout')).toBeUndefined();
82
- expect(wrapper.findComponent(DirectorModal).props('show')).toBe(false);
83
- });
84
-
85
- it('closes the scoring modal without changing status or scores', async () => {
86
- const bout = createBout({ Status: 'Active', Score1: 6, Score2: 5 });
87
- const wrapper = mountBracket([bout]);
88
-
89
- wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
90
- await wrapper.vm.$nextTick();
91
- wrapper.findComponent(DirectorModal).vm.$emit('update:closeModal', bout);
92
- await wrapper.vm.$nextTick();
93
-
94
- expect(wrapper.vm.boutsData[0]).toMatchObject({ Status: 'Active', Score1: 6, Score2: 5 });
95
- expect(wrapper.emitted('update:bout')).toBeUndefined();
96
- expect(wrapper.emitted('close:directing-bout')).toEqual([[bout]]);
97
- expect(wrapper.findComponent(DirectorModal).props('show')).toBe(false);
98
- });
99
-
100
- it('emits submitted bout data without assigning completion fields', async () => {
101
- const bout = createBout({ Status: 'Active' });
102
- const submittedBout = { ...bout, Score1: 7 };
103
- const wrapper = mountBracket([bout]);
104
-
105
- wrapper.findComponent(DirectorModal).vm.$emit('submit:bout', submittedBout);
106
- await wrapper.vm.$nextTick();
107
-
108
- const emittedBout = wrapper.emitted('submit:bout')[0][0];
109
- expect(emittedBout).toEqual(submittedBout);
110
- expect(emittedBout.Status).toBe('Active');
111
- expect(emittedBout.Ended).toBeUndefined();
112
- });
113
-
114
- it('does not render a director-selection modal', () => {
115
- const wrapper = mountBracket();
116
-
117
- expect(wrapper.findComponent({ name: 'AssignDirectorModal' }).exists()).toBe(false);
118
- });
119
-
120
- it('renders optional planned suggestions without changing the directing contract', async () => {
121
- const bout = createBout();
122
- const plannedDirectingStations = [
123
- {
124
- RingId: 1,
125
- RingName: 'Ring 1',
126
- Color: '#2563eb',
127
- Director: { PersonId: 1001, DisplayName: 'Alex Morgan' }
128
- }
129
- ];
130
- const plannedBoutAssignments = [{ DEBoutId: bout.DEBoutId, RingId: 1 }];
131
- const wrapper = mountBracket([bout], {
132
- plannedDirectingStations,
133
- plannedBoutAssignments,
134
- viewerPersonId: 1001
135
- });
136
-
137
- expect(wrapper.findComponent(BracketDirectorAssignmentPanel).props('stations')).toEqual(plannedDirectingStations);
138
- expect(wrapper.findComponent(Tableau).props()).toMatchObject({
139
- plannedDirectingStations,
140
- plannedBoutAssignments,
141
- viewerPersonId: 1001
142
- });
143
-
144
- wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
145
- await wrapper.vm.$nextTick();
146
-
147
- expect(wrapper.emitted('update:directing-bout')).toEqual([[bout]]);
148
- expect(wrapper.emitted('assign:director')).toBeUndefined();
149
- });
150
-
151
- it('emits add-planned-director intent from the assignment panel', async () => {
152
- const wrapper = mountBracket([], { showPlannedDirectorAssignments: true });
153
-
154
- wrapper.findComponent(BracketDirectorAssignmentPanel).vm.$emit('request:add-planned-director');
155
- await wrapper.vm.$nextTick();
156
-
157
- expect(wrapper.emitted('request:add-planned-director')).toEqual([[]]);
158
- });
159
-
160
- it('does not render planned directing controls when explicitly disabled', () => {
161
- const wrapper = mountBracket(undefined, {
162
- showPlannedDirectorAssignments: false,
163
- showBoutDirectorAssignment: false
164
- });
165
-
166
- expect(wrapper.findComponent(BracketDirectorAssignmentPanel).exists()).toBe(false);
167
- expect(wrapper.findComponent(Tableau).props('plannedDirectingStations')).toEqual([]);
168
- });
169
- });
170
-
171
- describe('Bracket assignment defaults', () => {
172
- it('shows assignment actions and the director panel without opt-in props', () => {
173
- const wrapper = mountBracket();
174
- expect(wrapper.findComponent(Tableau).props('showBoutDirectorAssignment')).toBe(true);
175
- expect(wrapper.findComponent(BracketDirectorAssignmentPanel).exists()).toBe(true);
176
- wrapper.unmount();
177
- });
178
- });
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import { describe, expect, it } from 'vitest';
3
+ import DirectorModal from '../../../Molecules/Modals/DirectorModal/DirectorModal.vue';
4
+ import EditBoutModal from '../../../Molecules/Modals/EditBoutModal/EditBoutModal.vue';
5
+ import Tableau from '../../../Organisms/Containers/Tableau/Tableau.vue';
6
+ import BracketDirectorAssignmentPanel from '../../../Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.vue';
7
+ import Bracket from './Bracket.vue';
8
+
9
+ const createBout = (overrides = {}) => ({
10
+ BoutId: 42,
11
+ DEBoutId: 1,
12
+ RoundLabel: 'Final',
13
+ Status: 'Scheduled',
14
+ Score1: 4,
15
+ Score2: 3,
16
+ Person1: { PersonId: 1, DisplayName: 'Fencer One' },
17
+ Person2: { PersonId: 2, DisplayName: 'Fencer Two' },
18
+ ...overrides
19
+ });
20
+
21
+ const mountBracket = (bouts = [createBout()], optionalProps = {}) =>
22
+ shallowMount(Bracket, {
23
+ props: {
24
+ bouts,
25
+ eventRules: [],
26
+ hostingClubColors: {},
27
+ connectedToServer: true,
28
+ ...optionalProps
29
+ }
30
+ });
31
+
32
+ describe('Bracket bout interaction', () => {
33
+ it('emits directing intent for a scheduled bout and waits for Active props', async () => {
34
+ const bout = createBout();
35
+ const wrapper = mountBracket([bout]);
36
+
37
+ wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
38
+ await wrapper.vm.$nextTick();
39
+
40
+ expect(wrapper.emitted('update:directing-bout')).toEqual([[bout]]);
41
+ expect(wrapper.findComponent(DirectorModal).props('show')).toBe(false);
42
+
43
+ await wrapper.setProps({ bouts: [{ ...bout, Status: 'Active' }] });
44
+
45
+ expect(wrapper.findComponent(DirectorModal).props('show')).toBe(true);
46
+ expect(wrapper.findComponent(DirectorModal).props('bout').Status).toBe('Active');
47
+ });
48
+
49
+ it('resumes an active bout through the directing path', async () => {
50
+ const bout = createBout({ Status: 'Active' });
51
+ const wrapper = mountBracket([bout]);
52
+
53
+ wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
54
+ await wrapper.vm.$nextTick();
55
+
56
+ expect(wrapper.emitted('update:directing-bout')).toEqual([[bout]]);
57
+ expect(wrapper.findComponent(DirectorModal).props('show')).toBe(true);
58
+ });
59
+
60
+ it('opens completed bouts for editing instead of directing', async () => {
61
+ const bout = createBout({ Status: 'Completed' });
62
+ const wrapper = mountBracket([bout]);
63
+
64
+ wrapper.findComponent(Tableau).vm.$emit('action:editBout', bout);
65
+ await wrapper.vm.$nextTick();
66
+
67
+ expect(wrapper.findComponent(EditBoutModal).props('show')).toBe(true);
68
+ expect(wrapper.findComponent(DirectorModal).props('show')).toBe(false);
69
+ expect(wrapper.emitted('update:directing-bout')).toBeUndefined();
70
+ });
71
+
72
+ it('does not direct a BYE bout', async () => {
73
+ const bout = createBout({
74
+ Person2: { PersonId: 0, DisplayName: 'BYE' }
75
+ });
76
+ const wrapper = mountBracket([bout]);
77
+
78
+ wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
79
+ await wrapper.vm.$nextTick();
80
+
81
+ expect(wrapper.emitted('update:directing-bout')).toBeUndefined();
82
+ expect(wrapper.findComponent(DirectorModal).props('show')).toBe(false);
83
+ });
84
+
85
+ it('closes the scoring modal without changing status or scores', async () => {
86
+ const bout = createBout({ Status: 'Active', Score1: 6, Score2: 5 });
87
+ const wrapper = mountBracket([bout]);
88
+
89
+ wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
90
+ await wrapper.vm.$nextTick();
91
+ wrapper.findComponent(DirectorModal).vm.$emit('update:closeModal', bout);
92
+ await wrapper.vm.$nextTick();
93
+
94
+ expect(wrapper.vm.boutsData[0]).toMatchObject({ Status: 'Active', Score1: 6, Score2: 5 });
95
+ expect(wrapper.emitted('update:bout')).toBeUndefined();
96
+ expect(wrapper.emitted('close:directing-bout')).toEqual([[bout]]);
97
+ expect(wrapper.findComponent(DirectorModal).props('show')).toBe(false);
98
+ });
99
+
100
+ it('emits submitted bout data without assigning completion fields', async () => {
101
+ const bout = createBout({ Status: 'Active' });
102
+ const submittedBout = { ...bout, Score1: 7 };
103
+ const wrapper = mountBracket([bout]);
104
+
105
+ wrapper.findComponent(DirectorModal).vm.$emit('submit:bout', submittedBout);
106
+ await wrapper.vm.$nextTick();
107
+
108
+ const emittedBout = wrapper.emitted('submit:bout')[0][0];
109
+ expect(emittedBout).toEqual(submittedBout);
110
+ expect(emittedBout.Status).toBe('Active');
111
+ expect(emittedBout.Ended).toBeUndefined();
112
+ });
113
+
114
+ it('does not render a director-selection modal', () => {
115
+ const wrapper = mountBracket();
116
+
117
+ expect(wrapper.findComponent({ name: 'AssignDirectorModal' }).exists()).toBe(false);
118
+ });
119
+
120
+ it('renders optional planned suggestions without changing the directing contract', async () => {
121
+ const bout = createBout();
122
+ const plannedDirectingStations = [
123
+ {
124
+ RingId: 1,
125
+ RingName: 'Ring 1',
126
+ Color: '#2563eb',
127
+ Director: { PersonId: 1001, DisplayName: 'Alex Morgan' }
128
+ }
129
+ ];
130
+ const plannedBoutAssignments = [{ DEBoutId: bout.DEBoutId, RingId: 1 }];
131
+ const wrapper = mountBracket([bout], {
132
+ plannedDirectingStations,
133
+ plannedBoutAssignments,
134
+ viewerPersonId: 1001
135
+ });
136
+
137
+ expect(wrapper.findComponent(BracketDirectorAssignmentPanel).props('stations')).toEqual(plannedDirectingStations);
138
+ expect(wrapper.findComponent(Tableau).props()).toMatchObject({
139
+ plannedDirectingStations,
140
+ plannedBoutAssignments,
141
+ viewerPersonId: 1001
142
+ });
143
+
144
+ wrapper.findComponent(Tableau).vm.$emit('action:directingBout', bout);
145
+ await wrapper.vm.$nextTick();
146
+
147
+ expect(wrapper.emitted('update:directing-bout')).toEqual([[bout]]);
148
+ expect(wrapper.emitted('assign:director')).toBeUndefined();
149
+ });
150
+
151
+ it('emits add-planned-director intent from the assignment panel', async () => {
152
+ const wrapper = mountBracket([], { showPlannedDirectorAssignments: true });
153
+
154
+ wrapper.findComponent(BracketDirectorAssignmentPanel).vm.$emit('request:add-planned-director');
155
+ await wrapper.vm.$nextTick();
156
+
157
+ expect(wrapper.emitted('request:add-planned-director')).toEqual([[]]);
158
+ });
159
+
160
+ it('does not render planned directing controls when explicitly disabled', () => {
161
+ const wrapper = mountBracket(undefined, {
162
+ showPlannedDirectorAssignments: false,
163
+ showBoutDirectorAssignment: false
164
+ });
165
+
166
+ expect(wrapper.findComponent(BracketDirectorAssignmentPanel).exists()).toBe(false);
167
+ expect(wrapper.findComponent(Tableau).props('plannedDirectingStations')).toEqual([]);
168
+ });
169
+ });
170
+
171
+ describe('Bracket assignment defaults', () => {
172
+ it('shows assignment actions and the director panel without opt-in props', () => {
173
+ const wrapper = mountBracket();
174
+ expect(wrapper.findComponent(Tableau).props('showBoutDirectorAssignment')).toBe(true);
175
+ expect(wrapper.findComponent(BracketDirectorAssignmentPanel).exists()).toBe(true);
176
+ wrapper.unmount();
177
+ });
178
+
179
+ it('forwards removal of a right-column director without changing authoritative bout fields', async () => {
180
+ const station = { RingId: 1, RingName: 'Ring 1', Director: { PersonId: 99, DisplayName: 'Dave Smith' } };
181
+ const bout = createBout({ Status: 'Active', RefereeId: 99, RefName: 'Dave Smith' });
182
+ const wrapper = mountBracket([bout], { plannedDirectingStations: [station] });
183
+ wrapper.findComponent(BracketDirectorAssignmentPanel).vm.$emit('request:remove-planned-director', station);
184
+ expect(wrapper.emitted('request:remove-planned-director')).toEqual([[station]]);
185
+ expect(wrapper.emitted('update:bout')).toBeUndefined();
186
+ expect(wrapper.vm.boutsData).toEqual([bout]);
187
+ expect(wrapper.props('plannedDirectingStations')).toEqual([station]);
188
+ await wrapper.setProps({ plannedDirectingStations: [] });
189
+ expect(wrapper.findComponent(BracketDirectorAssignmentPanel).props('stations')).toEqual([]);
190
+ wrapper.unmount();
191
+ });
192
+
193
+ it('opens the staff-fed modal and forwards detached roster save intent without changing bout or station props', async () => {
194
+ const selected = {
195
+ RingId: 1,
196
+ RingName: 'Ring 1',
197
+ Director: { PersonId: 99, DisplayName: 'Dave Smith' },
198
+ Color: '#2563eb'
199
+ };
200
+ const wrapper = mountBracket(undefined, {
201
+ availableDirectors: [selected.Director],
202
+ rings: [{ RingId: 1, RingName: 'Ring 1' }],
203
+ plannedDirectingStations: [selected]
204
+ });
205
+ wrapper.findComponent(BracketDirectorAssignmentPanel).vm.$emit('request:add-planned-director');
206
+ await wrapper.vm.$nextTick();
207
+ const modal = wrapper.findComponent({ name: 'BracketDirectorsModal' });
208
+ expect(modal.props('show')).toBe(true);
209
+ expect(wrapper.emitted('request:add-planned-director')).toBeUndefined();
210
+ modal.vm.$emit('submit:stations', []);
211
+ await wrapper.vm.$nextTick();
212
+ expect(wrapper.emitted('update:planned-directing-stations')).toEqual([[[]]]);
213
+ expect(wrapper.props('plannedDirectingStations')).toEqual([selected]);
214
+ expect(wrapper.emitted('update:bout')).toBeUndefined();
215
+ expect(modal.props('show')).toBe(false);
216
+ wrapper.unmount();
217
+ });
218
+ });
219
+
220
+ describe('Bracket assignment toolbar', () => {
221
+ it.each(['request:redistribute-scheduled-bouts', 'request:refresh-assignments'])(
222
+ 'forwards %s without changing bouts or planned coverage',
223
+ async event => {
224
+ const bouts = [createBout(), createBout({ BoutId: 43, Status: 'Active', RefereeId: 99 })];
225
+ const stations = [{ RingId: 1, Director: { PersonId: 99, DisplayName: 'Dave Smith' } }];
226
+ const assignments = [{ DEBoutId: 1, RingId: 1 }];
227
+ const wrapper = mountBracket(bouts, {
228
+ availableDirectors: [stations[0].Director],
229
+ rings: [{ RingId: 1 }],
230
+ plannedDirectingStations: stations,
231
+ plannedBoutAssignments: assignments
232
+ });
233
+
234
+ wrapper.findComponent(BracketDirectorAssignmentPanel).vm.$emit(event);
235
+ await wrapper.vm.$nextTick();
236
+
237
+ expect(wrapper.emitted()).toEqual({ [event]: [[]] });
238
+ expect(wrapper.vm.boutsData).toEqual(bouts);
239
+ expect(wrapper.props('plannedDirectingStations')).toEqual(stations);
240
+ expect(wrapper.props('plannedBoutAssignments')).toEqual(assignments);
241
+ wrapper.unmount();
242
+ }
243
+ );
244
+ });
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <section>
2
+ <section class="flex min-h-full flex-col xl:min-h-[calc(100dvh-2rem)]">
3
3
  <EditEventsTopMenu :currentTab="'Bracket'" :tabs="tabs(role, isMobile)" @tabMenuClick="handleTabMenuClick" />
4
4
  <ToggleHeader
5
5
  :currentSelection="viewName"
@@ -17,7 +17,7 @@
17
17
  :statusText="connectedToServer ? 'Connected' : 'Disconnected'"
18
18
  />
19
19
 
20
- <div class="flex flex-col gap-4 xl:flex-row">
20
+ <div class="flex flex-1 flex-col gap-4 xl:flex-row">
21
21
  <div class="min-w-0 flex-1">
22
22
  <Tableau
23
23
  :bouts="sortedBouts"
@@ -40,7 +40,12 @@
40
40
  class="order-first flex-none xl:order-last"
41
41
  :stations="plannedDirectingStations"
42
42
  :showAddCard="showAddPlannedDirectorCard"
43
- @request:add-planned-director="$emit('request:add-planned-director')"
43
+ :canManageDirectors="canManagePlannedDirectors"
44
+ @request:manage-planned-directors="handleManagePlannedDirectors"
45
+ @request:redistribute-scheduled-bouts="$emit('request:redistribute-scheduled-bouts')"
46
+ @request:refresh-assignments="$emit('request:refresh-assignments')"
47
+ @request:add-planned-director="handleAddPlannedDirector"
48
+ @request:remove-planned-director="$emit('request:remove-planned-director', $event)"
44
49
  />
45
50
  </div>
46
51
 
@@ -64,6 +69,15 @@
64
69
  </div>
65
70
  </div>
66
71
 
72
+ <BracketDirectorsModal
73
+ v-if="canManagePlannedDirectors"
74
+ v-model:show="showPlannedDirectorsModal"
75
+ :availableDirectors="availableDirectors"
76
+ :rings="rings"
77
+ :stations="plannedDirectingStations"
78
+ @submit:stations="handlePlannedDirectorsSubmit"
79
+ />
80
+
67
81
  <DirectorModal
68
82
  :bout="selectedBout"
69
83
  :eventRules="eventRules"
@@ -96,6 +110,8 @@ import ToggleHeader from '../../../Organisms/Headers/ToggleHeader/ToggleHeader.v
96
110
  import ServerConnected from '../../../Molecules/Indicators/ServerConnected/ServerConnected.vue';
97
111
  import BaseText from '../../../Atoms/Text/BaseText.vue';
98
112
  import DirectorModal from '../../../Molecules/Modals/DirectorModal/DirectorModal.vue';
113
+ import BracketDirectorsModal from '../../../Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.vue';
114
+ import { cloneDirectorData } from '../../../Util/bracketDirectors.js';
99
115
  import EditBoutModal from '../../../Molecules/Modals/EditBoutModal/EditBoutModal.vue';
100
116
  import Tableau from '../../../Organisms/Containers/Tableau/Tableau.vue';
101
117
  import BracketDirectorAssignmentPanel from '../../../Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.vue';
@@ -121,6 +137,10 @@ export default {
121
137
  'penalty:remove',
122
138
  'penalty:update',
123
139
  'request:add-planned-director',
140
+ 'request:remove-planned-director',
141
+ 'request:redistribute-scheduled-bouts',
142
+ 'request:refresh-assignments',
143
+ 'update:planned-directing-stations',
124
144
  'request:bout-active',
125
145
  'request:bout-director',
126
146
  'submit:bout',
@@ -133,12 +153,15 @@ export default {
133
153
  EditBoutModal,
134
154
  DirectorModal,
135
155
  BracketDirectorAssignmentPanel,
156
+ BracketDirectorsModal,
136
157
  BaseText,
137
158
  ToggleHeader,
138
159
  ServerConnected,
139
160
  EditEventsTopMenu
140
161
  },
141
162
  props: {
163
+ availableDirectors: { type: Array, default: undefined },
164
+ rings: { type: Array, default: undefined },
142
165
  showBoutDirectorAssignment: { type: Boolean, default: true },
143
166
  bouts: {
144
167
  type: Array,
@@ -189,6 +212,7 @@ export default {
189
212
  return {
190
213
  emptyDesertIcon,
191
214
  showDirectorModal: false,
215
+ showPlannedDirectorsModal: false,
192
216
  showEditBoutModal: false,
193
217
  selectedBout: {},
194
218
  tabs,
@@ -198,6 +222,9 @@ export default {
198
222
  };
199
223
  },
200
224
  computed: {
225
+ canManagePlannedDirectors() {
226
+ return !this.isMobile && Array.isArray(this.availableDirectors) && Array.isArray(this.rings);
227
+ },
201
228
  isMobile() {
202
229
  return this.windowWidth < 768;
203
230
  },
@@ -236,6 +263,9 @@ export default {
236
263
  }
237
264
  },
238
265
  watch: {
266
+ isMobile(value) {
267
+ if (value) this.showPlannedDirectorsModal = false;
268
+ },
239
269
  bouts: {
240
270
  handler(newBouts) {
241
271
  this.boutsData = this.cloneBouts(newBouts);
@@ -253,6 +283,21 @@ export default {
253
283
  window.removeEventListener('resize', this.updateViewBasedOnScreenSize);
254
284
  },
255
285
  methods: {
286
+ handleAddPlannedDirector() {
287
+ if (this.canManagePlannedDirectors) {
288
+ this.handleManagePlannedDirectors();
289
+ return;
290
+ }
291
+ this.$emit('request:add-planned-director');
292
+ },
293
+ handleManagePlannedDirectors() {
294
+ if (!this.canManagePlannedDirectors) return;
295
+ this.showPlannedDirectorsModal = true;
296
+ },
297
+ handlePlannedDirectorsSubmit(stations) {
298
+ this.$emit('update:planned-directing-stations', cloneDirectorData(stations));
299
+ this.showPlannedDirectorsModal = false;
300
+ },
256
301
  handleBoutDirectorRequest(bout) {
257
302
  if (!this.canShowBoutDirectorAssignment) return;
258
303
  const currentBout = this.boutsData.find(item => String(item.BoutId) === String(bout?.BoutId));
@@ -40,3 +40,11 @@ export const plannedDirectingStations = [
40
40
  ];
41
41
 
42
42
  export const plannedSuggestionViewerId = plannedDirectingStations[0].Director.PersonId;
43
+
44
+ export const availableDirectors = plannedDirectingStations.map((station, index) => ({
45
+ ...station.Director,
46
+ Color: station.Color,
47
+ DefaultRingId: index < 2 ? station.RingId : null
48
+ }));
49
+
50
+ export const availableRings = plannedDirectingStations.map(({ RingId, RingName }) => ({ RingId, RingName }));