@dcrackel/hematournamentui 1.0.715 → 1.0.716

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.
@@ -1,403 +1,417 @@
1
- <template>
2
- <section>
3
- <EditEventsTopMenu :currentTab="'Checkin'" :tabs="tabs(role, isMobile)" @tabMenuClick="handleTabMenuClick" />
4
- <AttendanceSummary
5
- :event="event"
6
- :absent="absent"
7
- :present="present"
8
- :activeCount="activeRosterCount"
9
- :waitlistCount="waitlistCount"
10
- :capacity="eventCapacity"
11
- />
12
- <div class="w-full flex flex-row justify-center mt-1 md:mt-3">
13
- <section class="w-full">
14
- <div class="w-full flex flex-col justify-center items-center" style="text-align: center">
15
- <section class="grid grid-cols-2 w-full mb-3 py-2">
16
- <div class="col-start-1 col-end-2 flex justify-start">
17
- <FindOrAddPerson
18
- :persons="personsAll"
19
- :fencingClubs="fencingClubs"
20
- :addBorder="false"
21
- :addNewText="'Add New'"
22
- @new-person-submitted="handleNewPerson"
23
- @person-selected="handleAddExistingPerson"
24
- />
25
- </div>
26
- <div class="col-start-2 col-end-3 flex justify-end">
27
- <DropDownMenu
28
- :label="label"
29
- :items="items"
30
- :selectedItem="localSelectedItem"
31
- @update:selectedItem="handleSelectedItem"
32
- />
33
- </div>
34
- </section>
35
-
36
- <section v-if="loading" class="w-full">
37
- <div v-for="index in 6" :key="index">
38
- <FencerCardSkeleton class="mb-4" />
39
- </div>
40
- </section>
41
- <section v-if="!loading" class="w-full">
42
- <div v-if="activeRosterCount > 0" class="mb-2 flex items-center justify-between">
43
- <BaseText text="Active Roster" size="md" color="quaternary" weight="bold" />
44
- <BaseText :text="activeRosterSummary" size="sm" color="quinary" />
45
- </div>
46
- <div
47
- v-for="(person, index) in sortedActivePersons"
48
- :key="person.PersonId || person.Person?.PersonId || index"
49
- >
50
- <FencerCard
51
- :person="person"
52
- class="mb-4"
53
- :isLast="index === sortedActivePersons.length - 1"
54
- :isEdit="event.Status === 'planning' || event.Status === 'preparation'"
55
- @forward:toProfile="handleForwardToProfile"
56
- @update:status="handleStatus"
57
- @remove:fencer="handleRemoveFencer"
58
- @update:moveUp="handleMoveUp"
59
- @update:moveDown="handleMoveDown"
60
- @request:weaver-sign="openWeaverModal"
61
- />
62
- </div>
63
- <section v-if="waitlistCount > 0" class="mt-6 w-full">
64
- <div class="mb-2 flex items-center justify-between">
65
- <BaseText text="Waitlist" size="md" color="quaternary" weight="bold" />
66
- <BaseText :text="waitlistSummary" size="sm" color="quinary" />
67
- </div>
68
- <div
69
- v-for="(person, index) in sortedWaitlistedPersons"
70
- :key="person.PersonId || person.Person?.PersonId || index"
71
- class="mb-3"
72
- >
73
- <WaitlistFencerCard
74
- :person="person"
75
- :queuePosition="index + 1"
76
- @forward:toProfile="handleForwardToProfile"
77
- @move:active="handleMoveWaitlistToActive"
78
- @remove:fencer="handleRemoveWaitlistFencer"
79
- />
80
- </div>
81
- </section>
82
- </section>
83
- <div v-if="!loading && personsInTournament.length === 0" class="mb-10">
84
- <div class="flex items-center border border-dropdownSelect rounded-lg shadow-lg bg-poolBox">
85
- <BaseText
86
- text="Remember to assign staff to your event!"
87
- size="lg"
88
- color="quaternary"
89
- weight="bold"
90
- class="w-full text-center py-2 px-8"
91
- />
92
- </div>
93
- <img alt="Small desert scene with wind blowing." class="w-60 mt-8 mx-auto" :src="emptyDesertIcon" />
94
- <BaseText
95
- :color="'primaryHighlight'"
96
- :size="'lg'"
97
- :text="'0 fencers have been added!'"
98
- :weight="'normal'"
99
- data-testid="text-num-events"
100
- />
101
- <BaseText
102
- :color="'primaryHighlight'"
103
- :size="'md'"
104
- :text="'Click Add New button to get started.'"
105
- :weight="'normal'"
106
- data-testid="text-num-events"
107
- />
108
- </div>
109
- </div>
110
- </section>
111
- </div>
112
- <BaseModal
113
- :show="showWeaverModal"
114
- @update:show="
115
- v => {
116
- if (!v) closeWeaverModal();
117
- }
118
- "
119
- headerText="Confirm Waiver Signature"
120
- :bodyText="weaverModalBody"
121
- buttonText="Waiver Signed!"
122
- iconColor="dropdownSelect"
123
- iconBackgroundColor="toggleBackground"
124
- iconClass="ml-3"
125
- iconName="fa-file-signature"
126
- @submitModal="confirmWeaverSigned"
127
- />
128
- </section>
129
- </template>
130
-
131
- <script>
132
- import BaseText from '../../../Atoms/Text/BaseText.vue';
133
- import emptyDesertIcon from '../../../../assets/empty_desert_icon.png';
134
- import FencerCard from '../../../Organisms/Cards/FencerCard/FencerCard.vue';
135
- import WaitlistFencerCard from '../../../Organisms/Cards/WaitlistFencerCard/WaitlistFencerCard.vue';
136
- import EditEventsTopMenu from '../../Menus/EditEventsTopMenu/EditEventsTopMenu.vue';
137
- import DropDownMenu from '../../../Organisms/ComplexInputs/DropDown/DropDownMenu.vue';
138
- import FindOrAddPerson from '../../../Organisms/ComplexInputs/FindOrAddPerson/FindOrAddPerson.vue';
139
- import AttendanceSummary from '../../../Organisms/Headers/AttendanceSummary/AttendanceSummary.vue';
140
- import FencerCardSkeleton from '../../../Organisms/Skeletons/FencerCard/FencerCardSkeleton.vue';
141
- import BaseModal from '../../../Molecules/Modals/BaseModal/BaseModal.vue';
142
- import { tabs } from '../../../Util/tabs.js';
143
-
144
- const personSorters = {
145
- Seed: (a, b) => (a.Seed || 0) - (b.Seed || 0),
146
- FullName: (a, b) => a.Person.DisplayName.localeCompare(b.Person.DisplayName),
147
- HEMARating: (a, b) => {
148
- const ratingA = a.Person.HEMARatings?.[0]?.Rating || 0;
149
- const ratingB = b.Person.HEMARatings?.[0]?.Rating || 0;
150
- return ratingB - ratingA;
151
- },
152
- M2Rating: (a, b) => {
153
- const m2RatingA = a.Person.M2Ratings?.[0]?.Rating || 'U00';
154
- const m2RatingB = b.Person.M2Ratings?.[0]?.Rating || 'U00';
155
- return m2RatingA.localeCompare(m2RatingB);
156
- }
157
- };
158
-
159
- const waitlistStatuses = new Set(['waitlist', 'waitlisted', 'waitinglist']);
160
-
161
- const normalizeStatus = status =>
162
- String(status || '')
163
- .toLocaleLowerCase()
164
- .replace(/[\s_-]/g, '');
165
-
166
- const isWaitlistedPerson = person => waitlistStatuses.has(normalizeStatus(person?.Status));
167
-
168
- const getEventRuleValue = (event, ruleName) => {
169
- const rule = event?.EventRules?.find(eventRule => eventRule?.Rules?.RuleName === ruleName);
170
- return rule?.RuleValue;
171
- };
172
-
173
- const waitlistOrderFields = ['WaitlistPosition', 'WaitlistOrder', 'CreatedAt', 'Seed', 'PoolPosition'];
174
-
175
- const getFirstDefinedFieldValue = (record, fieldNames, fallback) => {
176
- const source = record || {};
177
- const fieldName = fieldNames.find(name => source[name] !== null && source[name] !== undefined);
178
- if (!fieldName) return fallback;
179
- return source[fieldName];
180
- };
181
-
182
- const normalizeOrderValue = value => {
183
- const numericValue = Number(value);
184
- return Number.isNaN(numericValue) ? value : numericValue;
185
- };
186
-
187
- const getWaitlistOrderValue = person => {
188
- const order = getFirstDefinedFieldValue(person, waitlistOrderFields, 0);
189
- return normalizeOrderValue(order);
190
- };
191
-
192
- export default {
193
- emits: [
194
- 'add:existing-person',
195
- 'add:person',
196
- 'forward:toProfile',
197
- 'move:waitlist-to-active',
198
- 'remove:fencer',
199
- 'remove:waitlist-fencer',
200
- 'tab:menu-click',
201
- 'update:moveDown',
202
- 'update:moveUp',
203
- 'update:person',
204
- 'weaver:sign'
205
- ],
206
- components: {
207
- WaitlistFencerCard,
208
- FencerCardSkeleton,
209
- AttendanceSummary,
210
- FindOrAddPerson,
211
- DropDownMenu,
212
- EditEventsTopMenu,
213
- FencerCard,
214
- BaseText,
215
- BaseModal
216
- },
217
- props: {
218
- personsAll: Array,
219
- event: {
220
- type: Object,
221
- required: true,
222
- default: () => ({})
223
- },
224
- fencingClubs: {
225
- type: Array,
226
- default: () => []
227
- },
228
- personsInTournament: {
229
- type: Array,
230
- default: () => []
231
- },
232
- loading: {
233
- type: Boolean,
234
- default: false
235
- },
236
- role: {
237
- type: String,
238
- default: 'Admin'
239
- }
240
- },
241
- data() {
242
- return {
243
- emptyDesertIcon,
244
- tabs,
245
- windowWidth: window.innerWidth,
246
- label: 'Sort by',
247
- items: [
248
- { text: 'Seed', link: 'Seed' },
249
- { text: 'Name', link: 'FullName' },
250
- { text: 'M2Rating', link: 'M2Rating' },
251
- { text: 'HEMARating', link: 'HEMARating' }
252
- ],
253
- localSelectedItem: { text: 'Seed', link: 'Seed' },
254
- showWeaverModal: false,
255
- pendingWeaverPerson: null
256
- };
257
- },
258
- mounted() {
259
- window.addEventListener('resize', this.handleResize);
260
- },
261
- beforeUnmount() {
262
- window.removeEventListener('resize', this.handleResize);
263
- },
264
- computed: {
265
- weaverModalBody() {
266
- const p = this.pendingWeaverPerson?.Person;
267
- const name = p?.DisplayName ?? 'This fencer';
268
- return `Has ${name} completed signing the waiver?`;
269
- },
270
- isMobile() {
271
- return this.windowWidth <= 748;
272
- },
273
- activePersons() {
274
- if (!Array.isArray(this.personsInTournament) || this.personsInTournament.length === 0) {
275
- return [];
276
- }
277
-
278
- return this.personsInTournament.filter(person => !isWaitlistedPerson(person));
279
- },
280
- waitlistedPersons() {
281
- if (!Array.isArray(this.personsInTournament) || this.personsInTournament.length === 0) {
282
- return [];
283
- }
284
-
285
- return this.personsInTournament.filter(isWaitlistedPerson);
286
- },
287
- sortedActivePersons() {
288
- if (this.activePersons.length === 0) {
289
- return [];
290
- }
291
-
292
- let sortedArray = [...this.activePersons]; // Clone array to prevent modifying props
293
-
294
- sortedArray.sort((a, b) => {
295
- if (!a || !b || !a.Person || !b.Person) {
296
- console.warn('Undefined person object detected:', a, b);
297
- return 0;
298
- }
299
-
300
- const sortPersons = personSorters[this.localSelectedItem.link];
301
- if (sortPersons) return sortPersons(a, b);
302
-
303
- console.warn('Unknown sorting key:', this.localSelectedItem.link);
304
- return 0;
305
- });
306
- return sortedArray;
307
- },
308
- sortedWaitlistedPersons() {
309
- return [...this.waitlistedPersons].sort((a, b) => {
310
- const orderA = getWaitlistOrderValue(a);
311
- const orderB = getWaitlistOrderValue(b);
312
-
313
- if (typeof orderA === 'number' && typeof orderB === 'number') {
314
- return orderA - orderB;
315
- }
316
-
317
- return String(orderA).localeCompare(String(orderB));
318
- });
319
- },
320
- activeRosterCount() {
321
- return this.activePersons.length;
322
- },
323
- waitlistCount() {
324
- return this.waitlistedPersons.length;
325
- },
326
- eventCapacity() {
327
- const capacity =
328
- this.event?.CutOffNumber ?? this.event?.MaxFencers ?? getEventRuleValue(this.event, 'CutOffNumber');
329
- if (capacity === 0 || capacity === '0') {
330
- return 'Unlimited';
331
- }
332
-
333
- return capacity || '';
334
- },
335
- activeRosterSummary() {
336
- if (!this.eventCapacity) {
337
- return `${this.activeRosterCount} active`;
338
- }
339
-
340
- return `${this.activeRosterCount} / ${this.eventCapacity}`;
341
- },
342
- waitlistSummary() {
343
- return `${this.waitlistCount} waiting`;
344
- },
345
- absent() {
346
- return this.activePersons.filter(person => person.Status === 'Absent').length;
347
- },
348
- present() {
349
- return this.activePersons.filter(person => person.Status === 'Present').length;
350
- }
351
- },
352
- methods: {
353
- openWeaverModal(person) {
354
- this.pendingWeaverPerson = person;
355
- this.showWeaverModal = true;
356
- },
357
- closeWeaverModal() {
358
- this.showWeaverModal = false;
359
- this.pendingWeaverPerson = null;
360
- },
361
- confirmWeaverSigned() {
362
- this.$emit('weaver:sign', this.pendingWeaverPerson);
363
- this.closeWeaverModal();
364
- },
365
- handleSelectedItem(selectedItem) {
366
- this.localSelectedItem = selectedItem;
367
- },
368
- handleStatus(person) {
369
- this.$emit('update:person', person);
370
- },
371
- handleNewPerson(value) {
372
- this.$emit('add:person', value);
373
- },
374
- handleAddExistingPerson(value) {
375
- this.$emit('add:existing-person', value);
376
- },
377
- handleTabMenuClick(value) {
378
- this.$emit('tab:menu-click', value);
379
- },
380
- handleRemoveFencer(value) {
381
- this.$emit('remove:fencer', value);
382
- },
383
- handleMoveWaitlistToActive(person) {
384
- this.$emit('move:waitlist-to-active', person);
385
- },
386
- handleRemoveWaitlistFencer(person) {
387
- this.$emit('remove:waitlist-fencer', person);
388
- },
389
- handleMoveUp(person) {
390
- this.$emit('update:moveUp', person);
391
- },
392
- handleMoveDown(person) {
393
- this.$emit('update:moveDown', person);
394
- },
395
- handleForwardToProfile(personId) {
396
- this.$emit('forward:toProfile', personId);
397
- },
398
- handleResize() {
399
- this.windowWidth = window.innerWidth;
400
- }
401
- }
402
- };
403
- </script>
1
+ <template>
2
+ <section>
3
+ <EditEventsTopMenu :currentTab="'Checkin'" :tabs="tabs(role, isMobile)" @tabMenuClick="handleTabMenuClick" />
4
+ <AttendanceSummary
5
+ :event="event"
6
+ :absent="absent"
7
+ :present="present"
8
+ :activeCount="activeRosterCount"
9
+ :waitlistCount="waitlistCount"
10
+ :capacity="eventCapacity"
11
+ />
12
+ <div class="w-full flex flex-row justify-center mt-1 md:mt-3">
13
+ <section class="w-full">
14
+ <div class="w-full flex flex-col justify-center items-center" style="text-align: center">
15
+ <section class="grid grid-cols-2 w-full mb-3 py-2">
16
+ <div class="col-start-1 col-end-2 flex justify-start">
17
+ <FindOrAddPerson
18
+ :persons="personsAll"
19
+ :fencingClubs="fencingClubs"
20
+ :addBorder="false"
21
+ :addNewText="'Add New'"
22
+ @new-person-submitted="handleNewPerson"
23
+ @person-selected="handleAddExistingPerson"
24
+ />
25
+ </div>
26
+ <div class="col-start-2 col-end-3 flex justify-end">
27
+ <DropDownMenu
28
+ :label="label"
29
+ :items="items"
30
+ :selectedItem="localSelectedItem"
31
+ @update:selectedItem="handleSelectedItem"
32
+ />
33
+ </div>
34
+ </section>
35
+
36
+ <section v-if="loading" class="w-full">
37
+ <div v-for="index in 6" :key="index">
38
+ <FencerCardSkeleton class="mb-4" />
39
+ </div>
40
+ </section>
41
+ <section v-if="!loading" class="w-full">
42
+ <div v-if="activeRosterCount > 0" class="mb-2 flex items-center justify-between">
43
+ <BaseText text="Active Roster" size="md" color="quaternary" weight="bold" />
44
+ <BaseText :text="activeRosterSummary" size="sm" color="quinary" />
45
+ </div>
46
+ <div
47
+ v-for="(person, index) in sortedActivePersons"
48
+ :key="person.PersonId || person.Person?.PersonId || index"
49
+ >
50
+ <FencerCard
51
+ :person="person"
52
+ class="mb-4"
53
+ :isLast="index === sortedActivePersons.length - 1"
54
+ :isEdit="event.Status === 'planning' || event.Status === 'preparation'"
55
+ @forward:toProfile="handleForwardToProfile"
56
+ @update:status="handleStatus"
57
+ @remove:fencer="handleRemoveFencer"
58
+ @update:moveUp="handleMoveUp"
59
+ @update:moveDown="handleMoveDown"
60
+ @request:weaver-sign="openWeaverModal"
61
+ />
62
+ </div>
63
+ <section v-if="waitlistCount > 0" class="mt-6 w-full">
64
+ <div class="mb-2 flex items-center justify-between">
65
+ <BaseText text="Waitlist" size="md" color="quaternary" weight="bold" />
66
+ <BaseText :text="waitlistSummary" size="sm" color="quinary" />
67
+ </div>
68
+ <div
69
+ v-for="(person, index) in sortedWaitlistedPersons"
70
+ :key="person.PersonId || person.Person?.PersonId || index"
71
+ class="mb-3"
72
+ >
73
+ <WaitlistFencerCard
74
+ :person="person"
75
+ :queuePosition="index + 1"
76
+ :isNextCheckedIn="person === nextCheckedInPerson"
77
+ @update:check-in="handleWaitlistCheckIn"
78
+ @request:weaver-sign="openWeaverModal"
79
+ @forward:toProfile="handleForwardToProfile"
80
+ @move:active="handleMoveWaitlistToActive"
81
+ @remove:fencer="handleRemoveWaitlistFencer"
82
+ />
83
+ </div>
84
+ </section>
85
+ </section>
86
+ <div v-if="!loading && personsInTournament.length === 0" class="mb-10">
87
+ <div class="flex items-center border border-dropdownSelect rounded-lg shadow-lg bg-poolBox">
88
+ <BaseText
89
+ text="Remember to assign staff to your event!"
90
+ size="lg"
91
+ color="quaternary"
92
+ weight="bold"
93
+ class="w-full text-center py-2 px-8"
94
+ />
95
+ </div>
96
+ <img alt="Small desert scene with wind blowing." class="w-60 mt-8 mx-auto" :src="emptyDesertIcon" />
97
+ <BaseText
98
+ :color="'primaryHighlight'"
99
+ :size="'lg'"
100
+ :text="'0 fencers have been added!'"
101
+ :weight="'normal'"
102
+ data-testid="text-num-events"
103
+ />
104
+ <BaseText
105
+ :color="'primaryHighlight'"
106
+ :size="'md'"
107
+ :text="'Click Add New button to get started.'"
108
+ :weight="'normal'"
109
+ data-testid="text-num-events"
110
+ />
111
+ </div>
112
+ </div>
113
+ </section>
114
+ </div>
115
+ <BaseModal
116
+ :show="showWeaverModal"
117
+ @update:show="
118
+ v => {
119
+ if (!v) closeWeaverModal();
120
+ }
121
+ "
122
+ headerText="Confirm Waiver Signature"
123
+ :bodyText="weaverModalBody"
124
+ buttonText="Waiver Signed!"
125
+ iconColor="dropdownSelect"
126
+ iconBackgroundColor="toggleBackground"
127
+ iconClass="ml-3"
128
+ iconName="fa-file-signature"
129
+ @submitModal="confirmWeaverSigned"
130
+ />
131
+ </section>
132
+ </template>
133
+
134
+ <script>
135
+ import BaseText from '../../../Atoms/Text/BaseText.vue';
136
+ import emptyDesertIcon from '../../../../assets/empty_desert_icon.png';
137
+ import FencerCard from '../../../Organisms/Cards/FencerCard/FencerCard.vue';
138
+ import WaitlistFencerCard from '../../../Organisms/Cards/WaitlistFencerCard/WaitlistFencerCard.vue';
139
+ import EditEventsTopMenu from '../../Menus/EditEventsTopMenu/EditEventsTopMenu.vue';
140
+ import DropDownMenu from '../../../Organisms/ComplexInputs/DropDown/DropDownMenu.vue';
141
+ import FindOrAddPerson from '../../../Organisms/ComplexInputs/FindOrAddPerson/FindOrAddPerson.vue';
142
+ import AttendanceSummary from '../../../Organisms/Headers/AttendanceSummary/AttendanceSummary.vue';
143
+ import FencerCardSkeleton from '../../../Organisms/Skeletons/FencerCard/FencerCardSkeleton.vue';
144
+ import BaseModal from '../../../Molecules/Modals/BaseModal/BaseModal.vue';
145
+ import { tabs } from '../../../Util/tabs.js';
146
+ import { isWaitlistCheckedIn } from '../../../Util/waitlistAttendance.js';
147
+
148
+ const personSorters = {
149
+ Seed: (a, b) => (a.Seed || 0) - (b.Seed || 0),
150
+ FullName: (a, b) => a.Person.DisplayName.localeCompare(b.Person.DisplayName),
151
+ HEMARating: (a, b) => {
152
+ const ratingA = a.Person.HEMARatings?.[0]?.Rating || 0;
153
+ const ratingB = b.Person.HEMARatings?.[0]?.Rating || 0;
154
+ return ratingB - ratingA;
155
+ },
156
+ M2Rating: (a, b) => {
157
+ const m2RatingA = a.Person.M2Ratings?.[0]?.Rating || 'U00';
158
+ const m2RatingB = b.Person.M2Ratings?.[0]?.Rating || 'U00';
159
+ return m2RatingA.localeCompare(m2RatingB);
160
+ }
161
+ };
162
+
163
+ const waitlistStatuses = new Set(['waitlist', 'waitlisted', 'waitinglist']);
164
+
165
+ const normalizeStatus = status =>
166
+ String(status || '')
167
+ .toLocaleLowerCase()
168
+ .replace(/[\s_-]/g, '');
169
+
170
+ const isWaitlistedPerson = person => waitlistStatuses.has(normalizeStatus(person?.Status));
171
+
172
+ const getEventRuleValue = (event, ruleName) => {
173
+ const rule = event?.EventRules?.find(eventRule => eventRule?.Rules?.RuleName === ruleName);
174
+ return rule?.RuleValue;
175
+ };
176
+
177
+ const waitlistOrderFields = ['WaitlistPosition', 'WaitlistOrder', 'CreatedAt', 'Seed', 'PoolPosition'];
178
+
179
+ const getFirstDefinedFieldValue = (record, fieldNames, fallback) => {
180
+ const source = record || {};
181
+ const fieldName = fieldNames.find(name => source[name] !== null && source[name] !== undefined);
182
+ if (!fieldName) return fallback;
183
+ return source[fieldName];
184
+ };
185
+
186
+ const normalizeOrderValue = value => {
187
+ const numericValue = Number(value);
188
+ return Number.isNaN(numericValue) ? value : numericValue;
189
+ };
190
+
191
+ const getWaitlistOrderValue = person => {
192
+ const order = getFirstDefinedFieldValue(person, waitlistOrderFields, 0);
193
+ return normalizeOrderValue(order);
194
+ };
195
+
196
+ export default {
197
+ emits: [
198
+ 'add:existing-person',
199
+ 'add:person',
200
+ 'forward:toProfile',
201
+ 'move:waitlist-to-active',
202
+ 'remove:fencer',
203
+ 'remove:waitlist-fencer',
204
+ 'tab:menu-click',
205
+ 'update:moveDown',
206
+ 'update:moveUp',
207
+ 'update:person',
208
+ 'update:waitlist-check-in',
209
+ 'weaver:sign'
210
+ ],
211
+ components: {
212
+ WaitlistFencerCard,
213
+ FencerCardSkeleton,
214
+ AttendanceSummary,
215
+ FindOrAddPerson,
216
+ DropDownMenu,
217
+ EditEventsTopMenu,
218
+ FencerCard,
219
+ BaseText,
220
+ BaseModal
221
+ },
222
+ props: {
223
+ personsAll: Array,
224
+ event: {
225
+ type: Object,
226
+ required: true,
227
+ default: () => ({})
228
+ },
229
+ fencingClubs: {
230
+ type: Array,
231
+ default: () => []
232
+ },
233
+ personsInTournament: {
234
+ type: Array,
235
+ default: () => []
236
+ },
237
+ loading: {
238
+ type: Boolean,
239
+ default: false
240
+ },
241
+ role: {
242
+ type: String,
243
+ default: 'Admin'
244
+ }
245
+ },
246
+ data() {
247
+ return {
248
+ emptyDesertIcon,
249
+ tabs,
250
+ windowWidth: window.innerWidth,
251
+ label: 'Sort by',
252
+ items: [
253
+ { text: 'Seed', link: 'Seed' },
254
+ { text: 'Name', link: 'FullName' },
255
+ { text: 'M2Rating', link: 'M2Rating' },
256
+ { text: 'HEMARating', link: 'HEMARating' }
257
+ ],
258
+ localSelectedItem: { text: 'Seed', link: 'Seed' },
259
+ showWeaverModal: false,
260
+ pendingWeaverPerson: null
261
+ };
262
+ },
263
+ mounted() {
264
+ window.addEventListener('resize', this.handleResize);
265
+ },
266
+ beforeUnmount() {
267
+ window.removeEventListener('resize', this.handleResize);
268
+ },
269
+ computed: {
270
+ weaverModalBody() {
271
+ const p = this.pendingWeaverPerson?.Person;
272
+ const name = p?.DisplayName ?? 'This fencer';
273
+ return `Has ${name} completed signing the waiver?`;
274
+ },
275
+ isMobile() {
276
+ return this.windowWidth <= 748;
277
+ },
278
+ activePersons() {
279
+ if (!Array.isArray(this.personsInTournament) || this.personsInTournament.length === 0) {
280
+ return [];
281
+ }
282
+
283
+ return this.personsInTournament.filter(person => !isWaitlistedPerson(person));
284
+ },
285
+ waitlistedPersons() {
286
+ if (!Array.isArray(this.personsInTournament) || this.personsInTournament.length === 0) {
287
+ return [];
288
+ }
289
+
290
+ return this.personsInTournament.filter(isWaitlistedPerson);
291
+ },
292
+ sortedActivePersons() {
293
+ if (this.activePersons.length === 0) {
294
+ return [];
295
+ }
296
+
297
+ let sortedArray = [...this.activePersons]; // Clone array to prevent modifying props
298
+
299
+ sortedArray.sort((a, b) => {
300
+ if (!a || !b || !a.Person || !b.Person) {
301
+ console.warn('Undefined person object detected:', a, b);
302
+ return 0;
303
+ }
304
+
305
+ const sortPersons = personSorters[this.localSelectedItem.link];
306
+ if (sortPersons) return sortPersons(a, b);
307
+
308
+ console.warn('Unknown sorting key:', this.localSelectedItem.link);
309
+ return 0;
310
+ });
311
+ return sortedArray;
312
+ },
313
+ sortedWaitlistedPersons() {
314
+ return [...this.waitlistedPersons].sort((a, b) => {
315
+ const orderA = getWaitlistOrderValue(a);
316
+ const orderB = getWaitlistOrderValue(b);
317
+
318
+ if (typeof orderA === 'number' && typeof orderB === 'number') {
319
+ return orderA - orderB;
320
+ }
321
+
322
+ return String(orderA).localeCompare(String(orderB));
323
+ });
324
+ },
325
+ activeRosterCount() {
326
+ return this.activePersons.length;
327
+ },
328
+ waitlistCount() {
329
+ return this.waitlistedPersons.length;
330
+ },
331
+ waitlistCheckedInCount() {
332
+ return this.waitlistedPersons.filter(isWaitlistCheckedIn).length;
333
+ },
334
+ nextCheckedInPerson() {
335
+ return this.sortedWaitlistedPersons.find(isWaitlistCheckedIn) || null;
336
+ },
337
+ eventCapacity() {
338
+ const capacity =
339
+ this.event?.CutOffNumber ?? this.event?.MaxFencers ?? getEventRuleValue(this.event, 'CutOffNumber');
340
+ if (capacity === 0 || capacity === '0') {
341
+ return 'Unlimited';
342
+ }
343
+
344
+ return capacity || '';
345
+ },
346
+ activeRosterSummary() {
347
+ if (!this.eventCapacity) {
348
+ return `${this.activeRosterCount} active`;
349
+ }
350
+
351
+ return `${this.activeRosterCount} / ${this.eventCapacity}`;
352
+ },
353
+ waitlistSummary() {
354
+ return `${this.waitlistCount} waiting · ${this.waitlistCheckedInCount} checked in`;
355
+ },
356
+ absent() {
357
+ return this.activePersons.filter(person => person.Status === 'Absent').length;
358
+ },
359
+ present() {
360
+ return this.activePersons.filter(person => person.Status === 'Present').length;
361
+ }
362
+ },
363
+ methods: {
364
+ openWeaverModal(person) {
365
+ this.pendingWeaverPerson = person;
366
+ this.showWeaverModal = true;
367
+ },
368
+ closeWeaverModal() {
369
+ this.showWeaverModal = false;
370
+ this.pendingWeaverPerson = null;
371
+ },
372
+ confirmWeaverSigned() {
373
+ this.$emit('weaver:sign', this.pendingWeaverPerson);
374
+ this.closeWeaverModal();
375
+ },
376
+ handleSelectedItem(selectedItem) {
377
+ this.localSelectedItem = selectedItem;
378
+ },
379
+ handleStatus(person) {
380
+ this.$emit('update:person', person);
381
+ },
382
+ handleNewPerson(value) {
383
+ this.$emit('add:person', value);
384
+ },
385
+ handleAddExistingPerson(value) {
386
+ this.$emit('add:existing-person', value);
387
+ },
388
+ handleTabMenuClick(value) {
389
+ this.$emit('tab:menu-click', value);
390
+ },
391
+ handleRemoveFencer(value) {
392
+ this.$emit('remove:fencer', value);
393
+ },
394
+ handleWaitlistCheckIn(person) {
395
+ this.$emit('update:waitlist-check-in', person);
396
+ },
397
+ handleMoveWaitlistToActive(person) {
398
+ this.$emit('move:waitlist-to-active', person);
399
+ },
400
+ handleRemoveWaitlistFencer(person) {
401
+ this.$emit('remove:waitlist-fencer', person);
402
+ },
403
+ handleMoveUp(person) {
404
+ this.$emit('update:moveUp', person);
405
+ },
406
+ handleMoveDown(person) {
407
+ this.$emit('update:moveDown', person);
408
+ },
409
+ handleForwardToProfile(personId) {
410
+ this.$emit('forward:toProfile', personId);
411
+ },
412
+ handleResize() {
413
+ this.windowWidth = window.innerWidth;
414
+ }
415
+ }
416
+ };
417
+ </script>