@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
@@ -0,0 +1,138 @@
1
+ import { afterEach, describe, expect, it } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import BasicEventInfo from './BasicEventInfo/BasicEventInfo.vue';
4
+ import EditEventWizard from './EditEventWizard.vue';
5
+ import DropDownMenu from '../../ComplexInputs/DropDown/DropDownMenu.vue';
6
+
7
+ const wrappers = [];
8
+ const weapons = [
9
+ { text: 'Sword & Buckler', link: 8 },
10
+ { text: 'MG Longsword', link: 17 },
11
+ { text: 'Longsword', link: 1 }
12
+ ];
13
+
14
+ afterEach(() => wrappers.splice(0).forEach(wrapper => wrapper.unmount()));
15
+
16
+ function mountBasic(eventConfig, options = weapons) {
17
+ const wrapper = mount(BasicEventInfo, {
18
+ props: {
19
+ eventConfig,
20
+ weapons: options,
21
+ validation: { EventName: { invalid: false } },
22
+ steps: [],
23
+ currentStep: 0
24
+ },
25
+ global: { stubs: { DatePicker: true, StepIndicator: true } }
26
+ });
27
+ wrappers.push(wrapper);
28
+ return wrapper;
29
+ }
30
+
31
+ function selectedText(wrapper) {
32
+ return wrapper.findComponent(DropDownMenu).find('input').element.value;
33
+ }
34
+
35
+ describe('event weapon selection', () => {
36
+ it.each([1, '1', 8, '8', 17, '17'])('shows the actual saved weapon for ID %s without emitting a change', WeaponId => {
37
+ const wrapper = mountBasic({ WeaponId });
38
+ expect(selectedText(wrapper)).toBe(weapons.find(option => String(option.link) === String(WeaponId)).text);
39
+ expect(wrapper.emitted('update')).toBeUndefined();
40
+ });
41
+
42
+ it('accepts raw weapon records as well as dropdown options', () => {
43
+ const wrapper = mountBasic(
44
+ { WeaponId: '8' },
45
+ weapons.map(option => ({ Id: option.link, Name: option.text }))
46
+ );
47
+ expect(selectedText(wrapper)).toBe('Sword & Buckler');
48
+ expect(wrapper.findComponent(DropDownMenu).props('items')).toEqual(
49
+ weapons.map(option => ({ ...option, Id: option.link, Name: option.text }))
50
+ );
51
+ });
52
+
53
+ it.each([undefined, 99])('does not pretend an unavailable weapon %s is Longsword', WeaponId => {
54
+ const wrapper = mountBasic({ WeaponId });
55
+ expect(selectedText(wrapper)).toBe('Select a weapon');
56
+ expect(wrapper.emitted('update')).toBeUndefined();
57
+ });
58
+
59
+ it('reconciles a parent weapon change and restores that selection after closing the menu', async () => {
60
+ const wrapper = mountBasic({ WeaponId: 8 });
61
+ await wrapper.setProps({ eventConfig: { WeaponId: 1 } });
62
+ expect(selectedText(wrapper)).toBe('Longsword');
63
+ const dropdown = wrapper.findComponent(DropDownMenu);
64
+ dropdown.vm.clearInput();
65
+ dropdown.vm.restorePreviousValue();
66
+ await wrapper.vm.$nextTick();
67
+ expect(selectedText(wrapper)).toBe('Longsword');
68
+ expect(wrapper.emitted('update')).toBeUndefined();
69
+ });
70
+
71
+ it('shows the saved selection when the weapon options arrive later', async () => {
72
+ const wrapper = mountBasic({ WeaponId: 8 }, []);
73
+ await wrapper.setProps({ weapons });
74
+ expect(selectedText(wrapper)).toBe('Sword & Buckler');
75
+ expect(wrapper.emitted('update')).toBeUndefined();
76
+ });
77
+
78
+ it('emits the chosen option ID only after an explicit selection', async () => {
79
+ const wrapper = mountBasic({ WeaponId: 8 });
80
+ const dropdown = wrapper.findComponent(DropDownMenu);
81
+ dropdown.vm.handleDropDown();
82
+ await wrapper.vm.$nextTick();
83
+ await dropdown
84
+ .findAll('a')
85
+ .find(option => option.text() === 'Longsword')
86
+ .trigger('click');
87
+ expect(wrapper.emitted('update')).toEqual([[{ WeaponId: 1 }]]);
88
+ });
89
+
90
+ it('preserves the saved weapon when another field is edited and saves a deliberate weapon change', async () => {
91
+ const event = { EventId: 12, EventName: 'Example Event', EventPrice: 25, WeaponId: '8', rules: [] };
92
+ const wrapper = mount(EditEventWizard, {
93
+ props: { event, weapons, editMode: true },
94
+ global: { stubs: { DatePicker: true, StepIndicator: true } }
95
+ });
96
+ wrappers.push(wrapper);
97
+ await wrapper.vm.$nextTick();
98
+ expect(selectedText(wrapper)).toBe('Sword & Buckler');
99
+ wrapper.vm.handleValueChange({ EventName: 'Renamed Event' });
100
+ wrapper.vm.handleUpdate();
101
+ expect(wrapper.emitted('save:step')[0][0].event.WeaponId).toBe('8');
102
+
103
+ wrapper.findComponent(DropDownMenu).vm.handleClick(weapons.find(option => option.link === 17));
104
+ await wrapper.vm.$nextTick();
105
+ wrapper.vm.handleUpdate();
106
+ expect(wrapper.emitted('save:step')[1][0].event.WeaponId).toBe(17);
107
+ expect(event.WeaponId).toBe('8');
108
+ });
109
+ });
110
+
111
+ it.each([false, true])(
112
+ 'actual dropdown choices retain catalog IDs through submit with edit mode %s',
113
+ async editMode => {
114
+ const event = { EventId: 230, EventName: 'MG Longsword', EventPrice: 45, WeaponId: 17, rules: [] };
115
+ const wrapper = mount(EditEventWizard, {
116
+ props: { event, weapons, editMode },
117
+ global: { stubs: { DatePicker: true, StepIndicator: true } }
118
+ });
119
+ wrappers.push(wrapper);
120
+ await wrapper.vm.$nextTick();
121
+ expect(selectedText(wrapper)).toBe('MG Longsword');
122
+ for (const option of weapons) {
123
+ const dropdown = wrapper.findComponent(DropDownMenu);
124
+ dropdown.vm.handleDropDown();
125
+ await wrapper.vm.$nextTick();
126
+ await dropdown
127
+ .findAll('a')
128
+ .find(item => item.text() === option.text)
129
+ .trigger('click');
130
+ expect(selectedText(wrapper)).toBe(option.text);
131
+ wrapper.vm.handleSubmit();
132
+ const eventName = editMode ? 'save:step' : 'update:submit';
133
+ const submitted = wrapper.emitted(eventName).at(-1)[0];
134
+ expect((editMode ? submitted.event : submitted).WeaponId).toBe(option.link);
135
+ }
136
+ expect(event.WeaponId).toBe(17);
137
+ }
138
+ );
@@ -1,5 +1,6 @@
1
- import { ref, watch } from 'vue';
1
+ import { computed, nextTick, ref, watch } from 'vue';
2
2
  import Bracket from './Bracket.vue';
3
+ import { distributeStoryAssignments } from './BracketStoryAssignments.js';
3
4
  import {
4
5
  activateStoryBout,
5
6
  canStoryDirectorDirectBout,
@@ -8,11 +9,7 @@ import {
8
9
  getStoryDirectorId,
9
10
  replaceStoryBout
10
11
  } from './BracketStoryHarness.js';
11
- import {
12
- plannedBoutAssignments,
13
- plannedDirectingStations,
14
- plannedSuggestionViewerId
15
- } from './BracketPlannedSuggestions.js';
12
+ import { plannedDirectingStations, plannedSuggestionViewerId } from './BracketPlannedSuggestions.js';
16
13
  //import getDEWithBouts from '../../../../mocks/getDEWithBouts.js';
17
14
  import getDEWithBouts6Fencers from '../../../../mocks/getDeBouts-6fencers.js';
18
15
  import getDEWithBouts12Fencers from '../../../../mocks/getDeBouts-12fencers.js';
@@ -30,11 +27,54 @@ const renderInteractiveBracket = args => ({
30
27
  setup() {
31
28
  const storyBouts = ref(cloneStoryBouts(args.bouts));
32
29
  const storyStations = ref(cloneStations(args.plannedDirectingStations));
30
+ const storyAssignments = ref([]);
31
+ const canAddDirector = computed(() =>
32
+ plannedDirectingStations.some(
33
+ station => !storyStations.value.some(item => String(item.RingId) === String(station.RingId))
34
+ )
35
+ );
36
+ const redistributeAssignments = () => {
37
+ storyAssignments.value = distributeStoryAssignments({
38
+ bouts: storyBouts.value,
39
+ stations: storyStations.value,
40
+ previousAssignments: storyAssignments.value
41
+ });
42
+ };
43
+ const applyProvidedAssignments = assignments => {
44
+ redistributeAssignments();
45
+ const providedIds = new Set(assignments.map(assignment => String(assignment.DEBoutId)));
46
+ storyAssignments.value = [
47
+ ...storyAssignments.value.filter(assignment => !providedIds.has(String(assignment.DEBoutId))),
48
+ ...assignments.map(assignment => ({ ...assignment }))
49
+ ];
50
+ };
51
+ const assignmentBout = ref(null);
52
+ const directorPicker = ref(null);
53
+ const assignmentDialog = ref(null);
54
+
55
+ watch(() => args.plannedBoutAssignments, applyProvidedAssignments, { deep: true, immediate: true });
56
+
57
+ const requestBoutDirector = async bout => {
58
+ assignmentBout.value = bout;
59
+ await nextTick();
60
+ assignmentDialog.value?.showModal();
61
+ directorPicker.value?.focus();
62
+ };
63
+
64
+ const assignPlannedDirector = ringId => {
65
+ const bout = assignmentBout.value;
66
+ const station = storyStations.value.find(item => String(item.RingId) === String(ringId));
67
+ if (!bout || !station) return;
68
+ const remaining = storyAssignments.value.filter(item => String(item.DEBoutId) !== String(bout.DEBoutId));
69
+ storyAssignments.value = [...remaining, { DEBoutId: bout.DEBoutId, RingId: station.RingId }];
70
+ assignmentBout.value = null;
71
+ };
33
72
 
34
73
  watch(
35
74
  () => args.bouts,
36
75
  bouts => {
37
76
  storyBouts.value = cloneStoryBouts(bouts);
77
+ redistributeAssignments();
38
78
  },
39
79
  { deep: true }
40
80
  );
@@ -43,6 +83,7 @@ const renderInteractiveBracket = args => ({
43
83
  () => args.plannedDirectingStations,
44
84
  stations => {
45
85
  storyStations.value = cloneStations(stations);
86
+ redistributeAssignments();
46
87
  },
47
88
  { deep: true }
48
89
  );
@@ -59,6 +100,7 @@ const renderInteractiveBracket = args => ({
59
100
  const nextStation = plannedDirectingStations.find(station => !assignedRingIds.has(String(station.RingId)));
60
101
  if (!nextStation) return;
61
102
  storyStations.value = [...storyStations.value, ...cloneStations([nextStation])];
103
+ redistributeAssignments();
62
104
  };
63
105
 
64
106
  const handleDirectingBout = bout => {
@@ -75,6 +117,13 @@ const renderInteractiveBracket = args => ({
75
117
  args,
76
118
  storyBouts,
77
119
  storyStations,
120
+ storyAssignments,
121
+ canAddDirector,
122
+ assignmentBout,
123
+ directorPicker,
124
+ assignmentDialog,
125
+ requestBoutDirector,
126
+ assignPlannedDirector,
78
127
  handleAddPlannedDirector,
79
128
  handleCloseDirectingBout,
80
129
  handleDirectingBout,
@@ -82,6 +131,19 @@ const renderInteractiveBracket = args => ({
82
131
  };
83
132
  },
84
133
  template: `
134
+ <div>
135
+ <dialog v-if="assignmentBout" ref="assignmentDialog" aria-label="Assign director" class="fixed inset-0 m-auto rounded border border-dropdownSelect bg-white p-4 shadow-lg backdrop:bg-black/20" @cancel="assignmentBout = null">
136
+ <label class="text-sm text-primaryHighlight">
137
+ Assign director to bout {{ assignmentBout.DEBoutId }}
138
+ <select ref="directorPicker" class="ml-2 rounded border border-dropdownSelect p-2" @change="assignPlannedDirector($event.target.value)">
139
+ <option value="">Choose director</option>
140
+ <option v-for="station in storyStations" :key="station.RingId" :value="station.RingId">
141
+ {{ station.Director?.DisplayName || 'Director TBD' }} / {{ station.RingName }}
142
+ </option>
143
+ </select>
144
+ </label>
145
+ <button type="button" class="ml-4 text-sm underline" @click="assignmentBout = null">Cancel</button>
146
+ </dialog>
85
147
  <Bracket
86
148
  v-bind="args"
87
149
  :connectedToServer="args.connectedToServer"
@@ -89,6 +151,9 @@ const renderInteractiveBracket = args => ({
89
151
  :hostingClubColors="args.hostingClubColors"
90
152
  :bouts="storyBouts"
91
153
  :plannedDirectingStations="storyStations"
154
+ :plannedBoutAssignments="storyAssignments"
155
+ :showAddPlannedDirectorCard="args.showAddPlannedDirectorCard && canAddDirector"
156
+ @request:bout-director="requestBoutDirector"
92
157
  @close:directing-bout="handleCloseDirectingBout"
93
158
  @request:add-planned-director="handleAddPlannedDirector"
94
159
  @update:directing-bout="handleDirectingBout"
@@ -96,6 +161,7 @@ const renderInteractiveBracket = args => ({
96
161
  @update:bout="persistStoryBout"
97
162
  @submit:bout="persistStoryBout"
98
163
  />
164
+ </div>
99
165
  `
100
166
  });
101
167
 
@@ -110,13 +176,18 @@ export default {
110
176
  hostingClubColors: getDEWithBouts.hostingClubColors,
111
177
  connectedToServer: true,
112
178
  role: 'admin',
113
- plannedDirectingStations: [],
179
+ plannedDirectingStations: plannedDirectingStations.slice(0, 2),
114
180
  plannedBoutAssignments: [],
115
- viewerPersonId: null,
116
- showPlannedDirectorAssignments: false,
117
- showAddPlannedDirectorCard: true
181
+ viewerPersonId: plannedSuggestionViewerId,
182
+ showPlannedDirectorAssignments: true,
183
+ showAddPlannedDirectorCard: true,
184
+ showBoutDirectorAssignment: true
118
185
  },
119
186
  argTypes: {
187
+ showBoutDirectorAssignment: {
188
+ control: 'boolean',
189
+ description: 'Include Assign director in the pairing hover menu. The parent supplies staff selection and saving.'
190
+ },
120
191
  bouts: {
121
192
  control: 'object'
122
193
  },
@@ -220,10 +291,38 @@ export const PlannedDirectorSuggestions = {
220
291
  status: 'de',
221
292
  role: 'Admin',
222
293
  plannedDirectingStations: plannedDirectingStations.slice(0, 2),
223
- plannedBoutAssignments,
224
294
  viewerPersonId: plannedSuggestionViewerId,
225
295
  showPlannedDirectorAssignments: true,
226
- showAddPlannedDirectorCard: true
296
+ showAddPlannedDirectorCard: true,
297
+ showBoutDirectorAssignment: true
298
+ }
299
+ };
300
+
301
+ export const ThreeDirectors = {
302
+ args: { ...PlannedDirectorSuggestions.args, plannedDirectingStations: plannedDirectingStations.slice(0, 3) }
303
+ };
304
+
305
+ export const FourDirectors = {
306
+ args: { ...PlannedDirectorSuggestions.args, plannedDirectingStations: plannedDirectingStations.slice(0, 4) }
307
+ };
308
+
309
+ export const FiveDirectors = {
310
+ args: { ...PlannedDirectorSuggestions.args, plannedDirectingStations: plannedDirectingStations.slice(0, 5) }
311
+ };
312
+
313
+ export const SixDirectors = {
314
+ args: { ...PlannedDirectorSuggestions.args, plannedDirectingStations }
315
+ };
316
+
317
+ export const HoverBoutActions = {
318
+ args: { ...PlannedDirectorSuggestions.args },
319
+ parameters: {
320
+ docs: {
321
+ description: {
322
+ story:
323
+ 'Hover over a pairing for Open bout and Assign director. Clicking the pairing still opens it directly. The picker in this story simulates the consuming app; the story simulates balanced section coverage through the semifinals, while production assignment and persistence remain app-owned.'
324
+ }
325
+ }
227
326
  }
228
327
  };
229
328
 
@@ -246,3 +345,15 @@ export const noBoutsYet = {
246
345
  status: 'live'
247
346
  }
248
347
  };
348
+
349
+ export const PhoneDirector = {
350
+ args: { ...PlannedDirectorSuggestions.args, role: 'Director' },
351
+ parameters: {
352
+ docs: {
353
+ description: {
354
+ story:
355
+ 'Use a viewport narrower than 768px to see the phone directing view: a bout list, ring suggestions, connection status, and collapsed completed bouts. Organising controls stay on desktop, even when assignment options are enabled. Returning to desktop restores the selected List View or Tableau.'
356
+ }
357
+ }
358
+ }
359
+ };