@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.
- package/README.md +14 -0
- package/dist/HemaTournamentUI-lib.es.js +6917 -6255
- package/dist/HemaTournamentUI-lib.umd.js +27 -27
- package/dist/hematournamentui-tailwind.css +109 -18
- package/package.json +1 -1
- package/src/index.js +43 -42
- package/src/stories/Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.stories.js +49 -0
- package/src/stories/Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.test.js +129 -0
- package/src/stories/Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.vue +165 -0
- package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.stories.js +10 -1
- package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.test.js +31 -0
- package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.vue +20 -1
- package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.test.js +38 -0
- package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.vue +63 -9
- package/src/stories/Templates/EventManagement/Bracket/Bracket.stories.js +46 -4
- package/src/stories/Templates/EventManagement/Bracket/Bracket.test.js +244 -178
- package/src/stories/Templates/EventManagement/Bracket/Bracket.vue +48 -3
- package/src/stories/Templates/EventManagement/Bracket/BracketPlannedSuggestions.js +8 -0
- package/src/stories/Templates/EventManagement/Bracket/BracketStories.test.js +121 -0
- package/src/stories/Util/bracketDirectors.js +86 -0
|
@@ -77,4 +77,125 @@ describe('Bracket story assignment simulation', () => {
|
|
|
77
77
|
expect(shared.every(bout => getRing(bracket(), bout) === undefined)).toBe(true);
|
|
78
78
|
}
|
|
79
79
|
});
|
|
80
|
+
|
|
81
|
+
it.each(['Active', 'Completed'])(
|
|
82
|
+
'redistributes scheduled work after removal while preserving a %s bout',
|
|
83
|
+
async Status => {
|
|
84
|
+
const { bracket } = mountStory(stories.PlannedDirectorSuggestions);
|
|
85
|
+
const station = bracket().props('plannedDirectingStations')[0];
|
|
86
|
+
const bout = bracket()
|
|
87
|
+
.props('bouts')
|
|
88
|
+
.find(item => item.Person1.PersonId > 0 && item.Person2.PersonId > 0);
|
|
89
|
+
const savedBout = {
|
|
90
|
+
...bout,
|
|
91
|
+
Status,
|
|
92
|
+
RefereeId: station.Director.PersonId,
|
|
93
|
+
RefName: station.Director.DisplayName,
|
|
94
|
+
Score1: 4,
|
|
95
|
+
Score2: 3
|
|
96
|
+
};
|
|
97
|
+
bracket().vm.$emit('update:bout', savedBout);
|
|
98
|
+
await nextTick();
|
|
99
|
+
bracket().vm.$emit('request:remove-planned-director', station);
|
|
100
|
+
await nextTick();
|
|
101
|
+
expect(
|
|
102
|
+
bracket()
|
|
103
|
+
.props('plannedDirectingStations')
|
|
104
|
+
.map(item => item.RingId)
|
|
105
|
+
).toEqual([2]);
|
|
106
|
+
expect(
|
|
107
|
+
bracket()
|
|
108
|
+
.props('bouts')
|
|
109
|
+
.find(item => item.BoutId === bout.BoutId)
|
|
110
|
+
).toEqual(savedBout);
|
|
111
|
+
const scheduledIds = new Set(
|
|
112
|
+
bracket()
|
|
113
|
+
.props('bouts')
|
|
114
|
+
.filter(item => item.Status === 'Scheduled')
|
|
115
|
+
.map(item => item.DEBoutId)
|
|
116
|
+
);
|
|
117
|
+
expect(
|
|
118
|
+
bracket()
|
|
119
|
+
.props('plannedBoutAssignments')
|
|
120
|
+
.filter(item => scheduledIds.has(item.DEBoutId))
|
|
121
|
+
.every(item => item.RingId === 2)
|
|
122
|
+
).toBe(true);
|
|
123
|
+
expect(getRing(bracket(), savedBout)).toBe(1);
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
it('supports removing every director and adding a removed director again without changing story args', async () => {
|
|
128
|
+
const { bracket, args } = mountStory(stories.PlannedDirectorSuggestions);
|
|
129
|
+
for (const station of args.plannedDirectingStations) {
|
|
130
|
+
bracket().vm.$emit('request:remove-planned-director', station);
|
|
131
|
+
await nextTick();
|
|
132
|
+
}
|
|
133
|
+
expect(bracket().props('plannedDirectingStations')).toEqual([]);
|
|
134
|
+
expect(bracket().props('plannedBoutAssignments')).toEqual([]);
|
|
135
|
+
expect(bracket().props('showAddPlannedDirectorCard')).toBe(true);
|
|
136
|
+
bracket().vm.$emit('request:add-planned-director');
|
|
137
|
+
await nextTick();
|
|
138
|
+
expect(
|
|
139
|
+
bracket()
|
|
140
|
+
.props('plannedDirectingStations')
|
|
141
|
+
.map(station => station.RingId)
|
|
142
|
+
).toEqual([1]);
|
|
143
|
+
expect(
|
|
144
|
+
bracket()
|
|
145
|
+
.props('plannedBoutAssignments')
|
|
146
|
+
.every(item => item.RingId === 1)
|
|
147
|
+
).toBe(true);
|
|
148
|
+
expect(args.plannedDirectingStations).toHaveLength(2);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('keeps the signed-in director identity when resuming a bout after removing their planned station', async () => {
|
|
152
|
+
const { bracket } = mountStory(stories.PlannedDirectorSuggestions);
|
|
153
|
+
const station = bracket().props('plannedDirectingStations')[0];
|
|
154
|
+
const bout = bracket()
|
|
155
|
+
.props('bouts')
|
|
156
|
+
.find(item => item.Person1.PersonId > 0 && item.Person2.PersonId > 0);
|
|
157
|
+
bracket().vm.$emit('update:directing-bout', bout);
|
|
158
|
+
await nextTick();
|
|
159
|
+
const active = bracket()
|
|
160
|
+
.props('bouts')
|
|
161
|
+
.find(item => item.BoutId === bout.BoutId);
|
|
162
|
+
bracket().vm.$emit('request:remove-planned-director', station);
|
|
163
|
+
await nextTick();
|
|
164
|
+
bracket().vm.$emit('update:directing-bout', { ...active, Score1: 3 });
|
|
165
|
+
await nextTick();
|
|
166
|
+
expect(
|
|
167
|
+
bracket()
|
|
168
|
+
.props('bouts')
|
|
169
|
+
.find(item => item.BoutId === bout.BoutId)
|
|
170
|
+
).toMatchObject({
|
|
171
|
+
RefereeId: station.Director.PersonId,
|
|
172
|
+
RefName: station.Director.DisplayName,
|
|
173
|
+
Status: 'Active',
|
|
174
|
+
Score1: 3
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('applies staff-fed roster saves and ring moves only to DE plans', async () => {
|
|
179
|
+
const { bracket, args } = mountStory(stories.PlannedDirectorSuggestions);
|
|
180
|
+
const beforeBouts = JSON.parse(JSON.stringify(bracket().props('bouts')));
|
|
181
|
+
const original = bracket().props('plannedDirectingStations')[0];
|
|
182
|
+
const updated = { ...original, RingId: 3, RingName: 'Ring 3' };
|
|
183
|
+
bracket().vm.$emit('update:planned-directing-stations', [
|
|
184
|
+
updated,
|
|
185
|
+
...bracket().props('plannedDirectingStations').slice(1)
|
|
186
|
+
]);
|
|
187
|
+
await nextTick();
|
|
188
|
+
expect(bracket().props('plannedDirectingStations')[0]).toEqual(updated);
|
|
189
|
+
expect(
|
|
190
|
+
bracket()
|
|
191
|
+
.props('plannedBoutAssignments')
|
|
192
|
+
.every(item => [2, 3].includes(item.RingId))
|
|
193
|
+
).toBe(true);
|
|
194
|
+
expect(args.plannedDirectingStations[0].RingId).toBe(1);
|
|
195
|
+
expect(args.availableDirectors[0].DefaultRingId).toBe(1);
|
|
196
|
+
bracket().vm.$emit('update:planned-directing-stations', [updated]);
|
|
197
|
+
await nextTick();
|
|
198
|
+
expect(bracket().props('plannedDirectingStations')).toEqual([updated]);
|
|
199
|
+
expect(bracket().props('bouts')).toEqual(beforeBouts);
|
|
200
|
+
});
|
|
80
201
|
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export const getIdentityKey = value => String(value ?? '');
|
|
2
|
+
export const cloneDirectorData = value => JSON.parse(JSON.stringify(value));
|
|
3
|
+
const hasIdentity = value => value !== null && value !== undefined && String(value) !== '';
|
|
4
|
+
const isDirector = director => hasIdentity(director?.PersonId) && getIdentityKey(director.PersonId) !== '0';
|
|
5
|
+
|
|
6
|
+
export const getRingOptions = rings => {
|
|
7
|
+
const options = new Map();
|
|
8
|
+
for (const ring of rings || []) {
|
|
9
|
+
if (!hasIdentity(ring?.RingId)) continue;
|
|
10
|
+
options.set(getIdentityKey(ring.RingId), { ...ring, RingName: ring.RingName || 'Ring ' + ring.RingId });
|
|
11
|
+
}
|
|
12
|
+
return [...options.values()];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const getDirectorOptions = (availableDirectors, stations) => {
|
|
16
|
+
const options = new Map();
|
|
17
|
+
for (const director of availableDirectors || []) {
|
|
18
|
+
if (isDirector(director)) options.set(getIdentityKey(director.PersonId), cloneDirectorData(director));
|
|
19
|
+
}
|
|
20
|
+
for (const station of stations || []) {
|
|
21
|
+
const director = station?.Director;
|
|
22
|
+
if (!isDirector(director) || options.has(getIdentityKey(director.PersonId))) continue;
|
|
23
|
+
options.set(getIdentityKey(director.PersonId), cloneDirectorData(director));
|
|
24
|
+
}
|
|
25
|
+
return [...options.values()];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const createDirectorDraft = (availableDirectors, stations) => {
|
|
29
|
+
const stationByDirector = new Map(
|
|
30
|
+
(stations || [])
|
|
31
|
+
.filter(station => isDirector(station?.Director))
|
|
32
|
+
.map(station => [getIdentityKey(station.Director.PersonId), station])
|
|
33
|
+
);
|
|
34
|
+
return getDirectorOptions(availableDirectors, stations).map(candidate => {
|
|
35
|
+
const station = stationByDirector.get(getIdentityKey(candidate.PersonId));
|
|
36
|
+
const savedStation = station ? cloneDirectorData(station) : {};
|
|
37
|
+
const { DefaultRingId, Color, ...director } = candidate;
|
|
38
|
+
return {
|
|
39
|
+
Director: { ...savedStation.Director, ...director, DisplayName: director.DisplayName || 'Unnamed director' },
|
|
40
|
+
Color: savedStation.Color || Color || '',
|
|
41
|
+
RingId: savedStation.RingId ?? DefaultRingId ?? '',
|
|
42
|
+
Selected: Boolean(station),
|
|
43
|
+
station: savedStation
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const reconcileDirectorDraft = (availableDirectors, stations, draft) => {
|
|
49
|
+
const previous = new Map(draft.map(row => [getIdentityKey(row.Director.PersonId), row]));
|
|
50
|
+
return createDirectorDraft(availableDirectors, stations).map(row => {
|
|
51
|
+
const existing = previous.get(getIdentityKey(row.Director.PersonId));
|
|
52
|
+
if (!existing) return row;
|
|
53
|
+
return { ...row, Selected: existing.Selected, RingId: existing.RingId };
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const getDirectorDraftError = (draft, rings) => {
|
|
58
|
+
const selected = draft.filter(row => row.Selected);
|
|
59
|
+
const availableIds = new Set(
|
|
60
|
+
getRingOptions(rings)
|
|
61
|
+
.filter(ring => !ring.Disabled)
|
|
62
|
+
.map(ring => getIdentityKey(ring.RingId))
|
|
63
|
+
);
|
|
64
|
+
if (selected.some(row => !availableIds.has(getIdentityKey(row.RingId))))
|
|
65
|
+
return 'Choose a ring for each selected director.';
|
|
66
|
+
const selectedIds = selected.map(row => getIdentityKey(row.RingId));
|
|
67
|
+
if (new Set(selectedIds).size !== selectedIds.length) return 'Each selected director needs a different ring.';
|
|
68
|
+
return '';
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const createDirectorStations = (draft, rings) => {
|
|
72
|
+
if (getDirectorDraftError(draft, rings)) return null;
|
|
73
|
+
const ringById = new Map(getRingOptions(rings).map(ring => [getIdentityKey(ring.RingId), ring]));
|
|
74
|
+
return draft
|
|
75
|
+
.filter(row => row.Selected)
|
|
76
|
+
.map(row => {
|
|
77
|
+
const ring = ringById.get(getIdentityKey(row.RingId));
|
|
78
|
+
return cloneDirectorData({
|
|
79
|
+
...row.station,
|
|
80
|
+
RingId: ring.RingId,
|
|
81
|
+
RingName: ring.RingName,
|
|
82
|
+
Color: row.Color || ring.Color || '',
|
|
83
|
+
Director: row.Director
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
};
|