@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
@@ -0,0 +1,165 @@
1
+ <template>
2
+ <Teleport to="body">
3
+ <dialog
4
+ ref="dialog"
5
+ aria-label="DE directors"
6
+ data-testid="bracket-directors-modal"
7
+ class="fixed inset-0 m-auto max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-xl overflow-y-auto rounded-xl border border-dropdownSelect bg-neutral p-0 text-primaryHighlight shadow-xl backdrop:bg-secondary/60"
8
+ @cancel.prevent="close"
9
+ @click="close"
10
+ >
11
+ <form class="p-5" @submit.prevent="submit" @click.stop>
12
+ <div class="mb-4 flex items-start justify-between gap-3">
13
+ <div>
14
+ <h2 class="text-xl font-bold">Directors</h2>
15
+ <p class="mt-1 text-sm text-quaternary">Select DE directors and choose their rings.</p>
16
+ </div>
17
+ <button
18
+ type="button"
19
+ aria-label="Close director assignments"
20
+ class="flex h-8 w-8 flex-none items-center justify-center rounded text-xl text-quaternary hover:bg-dropdownSelect focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-bright"
21
+ @click="close"
22
+ >
23
+ &times;
24
+ </button>
25
+ </div>
26
+ <p v-if="!draft.length" class="rounded border border-dropdownSelect p-4 text-sm text-quaternary">
27
+ No directors available.
28
+ </p>
29
+ <div class="space-y-3">
30
+ <div
31
+ v-for="row in draft"
32
+ :key="row.Director.PersonId"
33
+ class="flex items-center gap-3 rounded-lg border border-dropdownSelect px-3 py-3"
34
+ >
35
+ <label class="flex min-w-0 flex-1 cursor-pointer items-center gap-3">
36
+ <input
37
+ v-model="row.Selected"
38
+ type="checkbox"
39
+ :data-director-id="row.Director.PersonId"
40
+ :aria-label="'Include ' + row.Director.DisplayName + ' in DE'"
41
+ class="h-5 w-5 flex-none accent-bright"
42
+ />
43
+ <span
44
+ v-if="row.Color"
45
+ class="h-2.5 w-2.5 flex-none rounded-full"
46
+ :style="{ backgroundColor: row.Color }"
47
+ aria-hidden="true"
48
+ ></span>
49
+ <span class="min-w-0 wrap-break-word text-sm font-semibold">{{ row.Director.DisplayName }}</span>
50
+ </label>
51
+ <select
52
+ v-model="row.RingId"
53
+ :disabled="!row.Selected"
54
+ :data-ring-director-id="row.Director.PersonId"
55
+ :aria-label="'Ring for ' + row.Director.DisplayName"
56
+ class="w-28 flex-none rounded border border-dropdownSelect bg-white px-2 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-bright disabled:opacity-50"
57
+ >
58
+ <option value="">Choose ring</option>
59
+ <option v-for="ring in ringOptions" :key="ring.RingId" :value="ring.RingId" :disabled="ring.Disabled">
60
+ {{ ring.RingName }}
61
+ </option>
62
+ </select>
63
+ </div>
64
+ </div>
65
+ <p v-if="error" role="alert" class="mt-3 text-sm text-alarm">{{ error }}</p>
66
+ <div class="mt-5 flex justify-end gap-2">
67
+ <button
68
+ type="button"
69
+ class="rounded border border-dropdownSelect px-4 py-2 text-sm hover:bg-dropdownSelect focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-bright"
70
+ @click="close"
71
+ >
72
+ Cancel
73
+ </button>
74
+ <button
75
+ type="submit"
76
+ :disabled="Boolean(error)"
77
+ class="rounded bg-primary px-4 py-2 text-sm font-semibold text-neutral hover:bg-primaryHighlight focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-bright disabled:cursor-not-allowed disabled:opacity-50"
78
+ >
79
+ Save assignments
80
+ </button>
81
+ </div>
82
+ </form>
83
+ </dialog>
84
+ </Teleport>
85
+ </template>
86
+
87
+ <script>
88
+ import {
89
+ createDirectorDraft,
90
+ reconcileDirectorDraft,
91
+ createDirectorStations,
92
+ getDirectorDraftError,
93
+ getRingOptions
94
+ } from '../../../Util/bracketDirectors.js';
95
+
96
+ export default {
97
+ name: 'BracketDirectorsModal',
98
+ emits: ['update:show', 'submit:stations'],
99
+ props: {
100
+ show: { type: Boolean, default: false },
101
+ availableDirectors: { type: Array, default: () => [] },
102
+ rings: { type: Array, default: () => [] },
103
+ stations: { type: Array, default: () => [] }
104
+ },
105
+ data() {
106
+ return { draft: [] };
107
+ },
108
+ computed: {
109
+ ringOptions() {
110
+ return getRingOptions(this.rings);
111
+ },
112
+ error() {
113
+ return getDirectorDraftError(this.draft, this.rings);
114
+ }
115
+ },
116
+ watch: {
117
+ show: {
118
+ immediate: true,
119
+ handler(value) {
120
+ if (value) this.resetDraft();
121
+ this.$nextTick(this.syncDialog);
122
+ }
123
+ },
124
+ stations: {
125
+ deep: true,
126
+ handler() {
127
+ if (this.show) this.resetDraft();
128
+ }
129
+ },
130
+ availableDirectors: {
131
+ deep: true,
132
+ handler() {
133
+ if (!this.show) return;
134
+ this.draft = reconcileDirectorDraft(this.availableDirectors, this.stations, this.draft);
135
+ }
136
+ }
137
+ },
138
+ mounted() {
139
+ this.syncDialog();
140
+ },
141
+ methods: {
142
+ resetDraft() {
143
+ this.draft = createDirectorDraft(this.availableDirectors, this.stations);
144
+ },
145
+ syncDialog() {
146
+ const dialog = this.$refs.dialog;
147
+ if (!dialog) return;
148
+ if (this.show && !dialog.open) {
149
+ dialog.showModal();
150
+ return;
151
+ }
152
+ if (!this.show && dialog.open) dialog.close();
153
+ },
154
+ close() {
155
+ this.$emit('update:show', false);
156
+ },
157
+ submit() {
158
+ const stations = createDirectorStations(this.draft, this.rings);
159
+ if (!stations) return;
160
+ this.$emit('submit:stations', stations);
161
+ this.close();
162
+ }
163
+ }
164
+ };
165
+ </script>
@@ -23,7 +23,16 @@ export default {
23
23
  }
24
24
  };
25
25
 
26
- export const Assigned = {};
26
+ export const Assigned = {
27
+ parameters: {
28
+ docs: {
29
+ description: {
30
+ story:
31
+ 'The remove control emits action:remove with a detached station payload. The parent updates planned coverage.'
32
+ }
33
+ }
34
+ }
35
+ };
27
36
 
28
37
  export const AddDirector = {
29
38
  args: {
@@ -42,4 +42,35 @@ describe('BracketDirectorAssignmentCard', () => {
42
42
  expect(wrapper.emitted('action:add')).toEqual([[]]);
43
43
  expect(wrapper.find('base-text-stub').attributes('text')).toBe('Click here to add director');
44
44
  });
45
+
46
+ it('emits a detached station when removing a director and waits for updated props', async () => {
47
+ const selectedStation = structuredClone(station);
48
+ const wrapper = shallowMount(BracketDirectorAssignmentCard, { props: { station: selectedStation } });
49
+ const removeButton = wrapper.get('[data-testid="remove-bracket-director"]');
50
+ expect(removeButton.attributes('aria-label')).toBe('Remove Sam Rivera from Ring 3');
51
+ await removeButton.trigger('click');
52
+ const payload = wrapper.emitted('action:remove')[0][0];
53
+ expect(payload).toEqual(selectedStation);
54
+ expect(payload).not.toBe(selectedStation);
55
+ payload.Director.DisplayName = 'Changed';
56
+ expect(selectedStation.Director.DisplayName).toBe('Sam Rivera');
57
+ expect(wrapper.find('[data-testid="bracket-director-assignment"]').exists()).toBe(true);
58
+ await wrapper.setProps({ station: null });
59
+ expect(wrapper.find('[data-testid="bracket-director-assignment"]').exists()).toBe(false);
60
+ });
61
+
62
+ it('has no remove control on the add card or an empty card', () => {
63
+ const addCard = shallowMount(BracketDirectorAssignmentCard, { props: { variant: 'add' } });
64
+ const emptyCard = shallowMount(BracketDirectorAssignmentCard);
65
+ expect(addCard.find('[data-testid="remove-bracket-director"]').exists()).toBe(false);
66
+ expect(emptyCard.find('[data-testid="remove-bracket-director"]').exists()).toBe(false);
67
+ });
68
+
69
+ it('displays the ring as text and reflects parent updates', async () => {
70
+ const wrapper = shallowMount(BracketDirectorAssignmentCard, { props: { station } });
71
+ expect(wrapper.find('select').exists()).toBe(false);
72
+ expect(wrapper.findAll('base-text-stub').map(text => text.attributes('text'))).toContain('Ring 3');
73
+ await wrapper.setProps({ station: { ...station, RingId: 1, RingName: 'Ring 1' } });
74
+ expect(wrapper.findAll('base-text-stub').map(text => text.attributes('text'))).toContain('Ring 1');
75
+ });
45
76
  });
@@ -45,6 +45,16 @@
45
45
  <BaseText :text="ringLabel" color="quaternary" size="xs" />
46
46
  <BaseText class="truncate" :text="directorName" color="primaryHighlight" size="sm" weight="bold" />
47
47
  </div>
48
+ <button
49
+ type="button"
50
+ data-testid="remove-bracket-director"
51
+ :aria-label="removeLabel"
52
+ :title="removeLabel"
53
+ class="flex h-8 w-8 flex-none items-center justify-center rounded text-quaternary transition hover:bg-dropdownSelect hover:text-alarm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-bright"
54
+ @click.stop="removeDirector"
55
+ >
56
+ <BaseIcon iconName="fa-xmark" color="none" size="sm" />
57
+ </button>
48
58
  </article>
49
59
  </template>
50
60
 
@@ -63,7 +73,7 @@ const getInitials = displayName =>
63
73
  export default {
64
74
  name: 'BracketDirectorAssignmentCard',
65
75
  components: { BaseIcon, BaseText },
66
- emits: ['action:add'],
76
+ emits: ['action:add', 'action:remove'],
67
77
  props: {
68
78
  station: {
69
79
  type: Object,
@@ -79,7 +89,16 @@ export default {
79
89
  default: 'Click here to add director'
80
90
  }
81
91
  },
92
+ methods: {
93
+ removeDirector() {
94
+ if (!this.station) return;
95
+ this.$emit('action:remove', JSON.parse(JSON.stringify(this.station)));
96
+ }
97
+ },
82
98
  computed: {
99
+ removeLabel() {
100
+ return 'Remove ' + this.directorName + ' from ' + this.ringLabel;
101
+ },
83
102
  assignmentLabel() {
84
103
  return `${this.ringLabel}, Director ${this.directorName}`;
85
104
  },
@@ -26,4 +26,42 @@ describe('BracketDirectorAssignmentPanel', () => {
26
26
 
27
27
  expect(wrapper.emitted('request:add-planned-director')).toEqual([[]]);
28
28
  });
29
+
30
+ it('forwards remove intent and reconciles the parent station list', async () => {
31
+ const wrapper = shallowMount(BracketDirectorAssignmentPanel, { props: { stations } });
32
+ wrapper.findComponent(BracketDirectorAssignmentCard).vm.$emit('action:remove', stations[0]);
33
+ expect(wrapper.emitted('request:remove-planned-director')).toEqual([[stations[0]]]);
34
+ expect(wrapper.props('stations')).toHaveLength(2);
35
+ await wrapper.setProps({ stations: stations.slice(1) });
36
+ expect(
37
+ wrapper
38
+ .findAllComponents(BracketDirectorAssignmentCard)
39
+ .filter(card => card.props('variant') !== 'add')
40
+ .map(card => card.props('station'))
41
+ ).toEqual([stations[1]]);
42
+ });
43
+
44
+ it.each([
45
+ ['Manage directors', 'request:manage-planned-directors'],
46
+ ['Redistribute scheduled bouts', 'request:redistribute-scheduled-bouts'],
47
+ ['Refresh assignments', 'request:refresh-assignments']
48
+ ])('emits %s intent without changing the station list', async (label, event) => {
49
+ const wrapper = shallowMount(BracketDirectorAssignmentPanel, {
50
+ props: { stations, canManageDirectors: true }
51
+ });
52
+ const button = wrapper.get('button[aria-label="' + label + '"]');
53
+ expect(button.attributes('title')).toBe(label);
54
+ await button.trigger('click');
55
+
56
+ expect(wrapper.emitted(event)).toEqual([[]]);
57
+ expect(Object.keys(wrapper.emitted()).filter(name => name.startsWith('request:'))).toEqual([event]);
58
+ expect(wrapper.props('stations')).toEqual(stations);
59
+ wrapper.unmount();
60
+ });
61
+
62
+ it('hides the toolbar when management is unavailable', () => {
63
+ const wrapper = shallowMount(BracketDirectorAssignmentPanel, { props: { stations } });
64
+ expect(wrapper.find('[role="group"]').exists()).toBe(false);
65
+ wrapper.unmount();
66
+ });
29
67
  });
@@ -1,20 +1,40 @@
1
1
  <template>
2
2
  <aside
3
3
  data-testid="bracket-director-assignment-panel"
4
- class="w-full rounded-xl border border-dropdownSelect bg-bannerBackground p-4 shadow-lg xl:sticky xl:top-4 xl:w-72"
4
+ aria-label="Director assignments"
5
+ class="w-full border-b border-dropdownSelect py-4 xl:w-72 xl:border-b-0 xl:border-l xl:pl-4"
5
6
  >
6
- <div class="mb-4">
7
- <BaseText color="primaryHighlight" size="md" text="Director Assignments" weight="bold" />
7
+ <header class="mb-4 space-y-3 border-b border-dropdownSelect pb-4">
8
+ <h2 class="text-md font-bold text-primaryHighlight">Director Assignments</h2>
9
+ <div v-if="canManageDirectors" class="flex flex-wrap gap-1" role="group" aria-label="Director assignment actions">
10
+ <button
11
+ v-for="action in directorActions"
12
+ :key="action.event"
13
+ type="button"
14
+ :data-testid="action.testId"
15
+ :aria-label="action.description"
16
+ :title="action.description"
17
+ class="flex min-h-9 flex-auto items-center justify-center gap-1 whitespace-nowrap rounded border border-dropdownSelect bg-white px-1 py-2 text-xs text-primaryHighlight transition hover:bg-dropdownSelect focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-bright"
18
+ @click="$emit(action.event)"
19
+ >
20
+ <BaseIcon :iconName="action.icon" iconStyle="fa" color="primaryHighlight" size="xs" aria-hidden="true" />
21
+ <span>{{ action.label }}</span>
22
+ </button>
23
+ </div>
24
+
8
25
  <BaseText
9
- class="mt-1"
10
26
  color="quaternary"
11
27
  size="xs"
12
28
  text="Plan ring coverage. Scheduled bouts may still be moved between rings."
13
29
  />
14
- </div>
15
-
30
+ </header>
16
31
  <div class="space-y-3">
17
- <BracketDirectorAssignmentCard v-for="station in stations" :key="station.RingId" :station="station" />
32
+ <BracketDirectorAssignmentCard
33
+ v-for="station in stations"
34
+ :key="station.RingId"
35
+ :station="station"
36
+ @action:remove="$emit('request:remove-planned-director', $event)"
37
+ />
18
38
  <BracketDirectorAssignmentCard
19
39
  v-if="showAddCard"
20
40
  variant="add"
@@ -26,13 +46,47 @@
26
46
 
27
47
  <script>
28
48
  import BaseText from '../../../../Atoms/Text/BaseText.vue';
49
+ import BaseIcon from '../../../../Atoms/Icon/BaseIcon.vue';
29
50
  import BracketDirectorAssignmentCard from '../../../Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.vue';
30
51
 
52
+ const directorActions = [
53
+ {
54
+ label: 'Manage',
55
+ description: 'Manage directors',
56
+ icon: 'fa-users',
57
+ event: 'request:manage-planned-directors',
58
+ testId: 'manage-bracket-directors'
59
+ },
60
+ {
61
+ label: 'Redistribute',
62
+ description: 'Redistribute scheduled bouts',
63
+ icon: 'fa-random',
64
+ event: 'request:redistribute-scheduled-bouts',
65
+ testId: 'redistribute-scheduled-bouts'
66
+ },
67
+ {
68
+ label: 'Refresh',
69
+ description: 'Refresh assignments',
70
+ icon: 'fa-refresh',
71
+ event: 'request:refresh-assignments',
72
+ testId: 'refresh-bracket-assignments'
73
+ }
74
+ ];
31
75
  export default {
32
76
  name: 'BracketDirectorAssignmentPanel',
33
- components: { BaseText, BracketDirectorAssignmentCard },
34
- emits: ['request:add-planned-director'],
77
+ components: { BaseIcon, BaseText, BracketDirectorAssignmentCard },
78
+ emits: [
79
+ 'request:add-planned-director',
80
+ 'request:remove-planned-director',
81
+ 'request:manage-planned-directors',
82
+ 'request:redistribute-scheduled-bouts',
83
+ 'request:refresh-assignments'
84
+ ],
85
+ data() {
86
+ return { directorActions };
87
+ },
35
88
  props: {
89
+ canManageDirectors: { type: Boolean, default: false },
36
90
  stations: {
37
91
  type: Array,
38
92
  default: () => []
@@ -9,7 +9,12 @@ import {
9
9
  getStoryDirectorId,
10
10
  replaceStoryBout
11
11
  } from './BracketStoryHarness.js';
12
- import { plannedDirectingStations, plannedSuggestionViewerId } from './BracketPlannedSuggestions.js';
12
+ import {
13
+ availableDirectors,
14
+ availableRings,
15
+ plannedDirectingStations,
16
+ plannedSuggestionViewerId
17
+ } from './BracketPlannedSuggestions.js';
13
18
  //import getDEWithBouts from '../../../../mocks/getDEWithBouts.js';
14
19
  import getDEWithBouts6Fencers from '../../../../mocks/getDeBouts-6fencers.js';
15
20
  import getDEWithBouts12Fencers from '../../../../mocks/getDeBouts-12fencers.js';
@@ -29,8 +34,9 @@ const renderInteractiveBracket = args => ({
29
34
  const storyStations = ref(cloneStations(args.plannedDirectingStations));
30
35
  const storyAssignments = ref([]);
31
36
  const canAddDirector = computed(() =>
32
- plannedDirectingStations.some(
33
- station => !storyStations.value.some(item => String(item.RingId) === String(station.RingId))
37
+ (args.availableDirectors || availableDirectors).some(
38
+ director =>
39
+ !storyStations.value.some(station => String(station.Director?.PersonId) === String(director.PersonId))
34
40
  )
35
41
  );
36
42
  const redistributeAssignments = () => {
@@ -93,7 +99,11 @@ const renderInteractiveBracket = args => ({
93
99
  };
94
100
 
95
101
  const getViewerStation = () =>
96
- storyStations.value.find(station => String(station.Director?.PersonId) === String(args.viewerPersonId));
102
+ [
103
+ ...storyStations.value,
104
+ ...plannedDirectingStations,
105
+ ...(args.availableDirectors || []).map(Director => ({ Director }))
106
+ ].find(station => String(station.Director?.PersonId) === String(args.viewerPersonId));
97
107
 
98
108
  const handleAddPlannedDirector = () => {
99
109
  const assignedRingIds = new Set(storyStations.value.map(station => String(station.RingId)));
@@ -125,6 +135,21 @@ const renderInteractiveBracket = args => ({
125
135
  requestBoutDirector,
126
136
  assignPlannedDirector,
127
137
  handleAddPlannedDirector,
138
+ redistributeAssignments,
139
+ refreshAssignments() {
140
+ storyStations.value = cloneStations(args.plannedDirectingStations);
141
+ applyProvidedAssignments(args.plannedBoutAssignments);
142
+ },
143
+ handleRemovePlannedDirector(station) {
144
+ const remainingStations = storyStations.value.filter(item => String(item.RingId) !== String(station?.RingId));
145
+ if (remainingStations.length === storyStations.value.length) return;
146
+ storyStations.value = remainingStations;
147
+ redistributeAssignments();
148
+ },
149
+ handlePlannedDirectorsUpdate(stations) {
150
+ storyStations.value = cloneStations(stations);
151
+ redistributeAssignments();
152
+ },
128
153
  handleCloseDirectingBout,
129
154
  handleDirectingBout,
130
155
  persistStoryBout
@@ -154,8 +179,12 @@ const renderInteractiveBracket = args => ({
154
179
  :plannedBoutAssignments="storyAssignments"
155
180
  :showAddPlannedDirectorCard="args.showAddPlannedDirectorCard && canAddDirector"
156
181
  @request:bout-director="requestBoutDirector"
182
+ @request:redistribute-scheduled-bouts="redistributeAssignments"
183
+ @request:refresh-assignments="refreshAssignments"
157
184
  @close:directing-bout="handleCloseDirectingBout"
158
185
  @request:add-planned-director="handleAddPlannedDirector"
186
+ @request:remove-planned-director="handleRemovePlannedDirector"
187
+ @update:planned-directing-stations="handlePlannedDirectorsUpdate"
159
188
  @update:directing-bout="handleDirectingBout"
160
189
  @request:bout-active="handleDirectingBout"
161
190
  @update:bout="persistStoryBout"
@@ -171,6 +200,8 @@ export default {
171
200
  tags: ['Bracket'],
172
201
  render: renderInteractiveBracket,
173
202
  args: {
203
+ availableDirectors,
204
+ rings: availableRings,
174
205
  bouts: getDEWithBouts.pools[0].Bouts,
175
206
  eventRules: getDEWithBouts.eventRules,
176
207
  hostingClubColors: getDEWithBouts.hostingClubColors,
@@ -184,6 +215,17 @@ export default {
184
215
  showBoutDirectorAssignment: true
185
216
  },
186
217
  argTypes: {
218
+ 'request:redistribute-scheduled-bouts': {
219
+ description: 'Parent intent to redistribute scheduled coverage without changing active or completed bouts.'
220
+ },
221
+ 'request:refresh-assignments': {
222
+ description: 'Parent intent to reload stations and assignments. This story reloads the supplied args fixtures.'
223
+ },
224
+ availableDirectors: {
225
+ control: 'object',
226
+ description: 'Eligible staff, including DE-only directors, supplied by the parent.'
227
+ },
228
+ rings: { control: 'object', description: 'Available rings supplied by the parent.' },
187
229
  showBoutDirectorAssignment: {
188
230
  control: 'boolean',
189
231
  description: 'Include Assign director in the pairing hover menu. The parent supplies staff selection and saving.'