@dcrackel/hematournamentui 1.0.710 → 1.0.712
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 +152 -148
- package/dist/HemaTournamentUI-lib.es.js +4172 -4046
- package/dist/HemaTournamentUI-lib.umd.js +27 -27
- package/dist/hematournamentui-tailwind.css +11 -0
- package/package.json +1 -1
- package/src/stories/Molecules/Indicators/RegistrationStatus/RegistrationStatus.vue +30 -0
- package/src/stories/Organisms/Cards/EventCard/EventCard.stories.js +41 -1
- package/src/stories/Organisms/Cards/EventCard/EventCard.test.js +106 -0
- package/src/stories/Organisms/Cards/EventCard/EventCard.vue +62 -30
- package/src/stories/Organisms/Cards/FencerCard/FencerCard.vue +1 -1
- package/src/stories/Organisms/Headers/AttendanceSummary/AttendanceSummary.vue +12 -5
- package/src/stories/Organisms/Wizards/EditEventWizard/BasicEventInfo/BasicEventInfo.stories.js +4 -0
- package/src/stories/Organisms/Wizards/EditEventWizard/BasicEventInfo/BasicEventInfo.vue +249 -227
- package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.stories.js +4 -0
- package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.test.js +221 -191
- package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.vue +372 -366
- package/src/stories/Organisms/Wizards/EditEventWizard/RuleSerialization.test.js +29 -0
- package/src/stories/Templates/EventManagement/EventAttendance/EventAttendance.stories.js +31 -1
- package/src/stories/Templates/TournamentManagement/ListEvents/ListEvents.stories.js +58 -45
- package/src/stories/Templates/TournamentManagement/TournamentManagement.test.js +27 -2
- package/src/stories/Templates/TournamentManagement/TournamentPageOne/TournamentPageOne.stories.js +1 -1
- package/src/stories/Templates/TournamentManagement/TournamentPageOne/TournamentPageOne.vue +17 -2
- package/src/stories/Util/registrationStatus.js +11 -0
|
@@ -530,6 +530,9 @@
|
|
|
530
530
|
.ml-5 {
|
|
531
531
|
margin-left: calc(var(--spacing) * 5);
|
|
532
532
|
}
|
|
533
|
+
.ml-6 {
|
|
534
|
+
margin-left: calc(var(--spacing) * 6);
|
|
535
|
+
}
|
|
533
536
|
.ml-9 {
|
|
534
537
|
margin-left: calc(var(--spacing) * 9);
|
|
535
538
|
}
|
|
@@ -1049,6 +1052,9 @@
|
|
|
1049
1052
|
margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)));
|
|
1050
1053
|
}
|
|
1051
1054
|
}
|
|
1055
|
+
.gap-y-1 {
|
|
1056
|
+
row-gap: calc(var(--spacing) * 1);
|
|
1057
|
+
}
|
|
1052
1058
|
.self-end {
|
|
1053
1059
|
align-self: flex-end;
|
|
1054
1060
|
}
|
|
@@ -2396,6 +2402,11 @@
|
|
|
2396
2402
|
flex-wrap: nowrap;
|
|
2397
2403
|
}
|
|
2398
2404
|
}
|
|
2405
|
+
.md\:items-stretch {
|
|
2406
|
+
@media (width >= 48rem) {
|
|
2407
|
+
align-items: stretch;
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2399
2410
|
.md\:justify-start {
|
|
2400
2411
|
@media (width >= 48rem) {
|
|
2401
2412
|
justify-content: flex-start;
|
package/package.json
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="statusLabel" class="inline-flex items-center gap-2 whitespace-nowrap">
|
|
3
|
+
<span class="sr-only">Registration status:</span>
|
|
4
|
+
<BaseIcon icon-name="fa-info-circle" size="xs" color="quaternary" aria-hidden="true" />
|
|
5
|
+
<BaseText :text="statusLabel" size="xs" color="quaternary" />
|
|
6
|
+
</div>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script>
|
|
10
|
+
import BaseIcon from '../../../Atoms/Icon/BaseIcon.vue';
|
|
11
|
+
import BaseText from '../../../Atoms/Text/BaseText.vue';
|
|
12
|
+
|
|
13
|
+
import { getRegistrationStatusLabel } from '../../../Util/registrationStatus.js';
|
|
14
|
+
|
|
15
|
+
export default {
|
|
16
|
+
name: 'RegistrationStatus',
|
|
17
|
+
components: { BaseIcon, BaseText },
|
|
18
|
+
props: {
|
|
19
|
+
status: {
|
|
20
|
+
type: String,
|
|
21
|
+
default: ''
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
computed: {
|
|
25
|
+
statusLabel() {
|
|
26
|
+
return getRegistrationStatusLabel(this.status);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
</script>
|
|
@@ -5,6 +5,14 @@ export default {
|
|
|
5
5
|
title: 'Organisms/Cards/EventCard',
|
|
6
6
|
component: EventCard,
|
|
7
7
|
tags: ['autodocs'],
|
|
8
|
+
parameters: {
|
|
9
|
+
docs: {
|
|
10
|
+
description: {
|
|
11
|
+
component:
|
|
12
|
+
'RegistrationStatus accepts open, waitlist, full, or closed from the consuming app. The icon tray displays a status icon and the current state using existing theme colors. These values are also supported in Status when RegistrationStatus is absent. Missing or unknown values hide the status indicator. Lifecycle Status values such as live and completed continue to control existing behavior.'
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
8
16
|
args: {
|
|
9
17
|
event: singleEventMock
|
|
10
18
|
},
|
|
@@ -12,5 +20,37 @@ export default {
|
|
|
12
20
|
};
|
|
13
21
|
|
|
14
22
|
export const Default = {
|
|
15
|
-
event: singleEventMock
|
|
23
|
+
args: { event: singleEventMock }
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const Open = {
|
|
27
|
+
args: { event: { ...singleEventMock, Status: 'registration', RegistrationStatus: 'open' } }
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const Waitlist = {
|
|
31
|
+
args: { event: { ...singleEventMock, Status: 'registration', RegistrationStatus: 'waitlist' } }
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const Full = {
|
|
35
|
+
args: { event: { ...singleEventMock, Status: 'registration', RegistrationStatus: 'full' } }
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const Closed = {
|
|
39
|
+
args: { event: { ...singleEventMock, RegistrationStatus: 'closed' } }
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const MissingSettings = {
|
|
43
|
+
args: { event: { ...singleEventMock, EventRules: [] } }
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const InvalidSavedLimit = {
|
|
47
|
+
args: {
|
|
48
|
+
event: {
|
|
49
|
+
...singleEventMock,
|
|
50
|
+
EventRules: [
|
|
51
|
+
...singleEventMock.EventRules.filter(rule => rule.RuleId !== 14),
|
|
52
|
+
{ RuleId: 14, RuleValue: 'function link() { [native code] }', Rules: { RuleName: 'CutOffNumber' } }
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
16
56
|
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { afterEach, expect, it } from 'vitest';
|
|
2
|
+
import { shallowMount } from '@vue/test-utils';
|
|
3
|
+
import EventCard from './EventCard.vue';
|
|
4
|
+
|
|
5
|
+
const wrappers = [];
|
|
6
|
+
afterEach(() => wrappers.splice(0).forEach(wrapper => wrapper.unmount()));
|
|
7
|
+
const nativeFunction = 'function link() { [native code] }';
|
|
8
|
+
|
|
9
|
+
function rule(RuleName, RuleValue) {
|
|
10
|
+
return { Rules: { RuleName }, RuleValue };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function mountCard(overrides = {}) {
|
|
14
|
+
const wrapper = shallowMount(EventCard, {
|
|
15
|
+
props: {
|
|
16
|
+
event: {
|
|
17
|
+
EventName: 'Example Event',
|
|
18
|
+
Date: '2026-10-17',
|
|
19
|
+
StartTime: '12:00',
|
|
20
|
+
Present: '8',
|
|
21
|
+
Absent: '0',
|
|
22
|
+
EventRules: [],
|
|
23
|
+
...overrides
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
global: { stubs: { BaseText: { props: ['text'], template: '<span>{{ text }}</span>' } } }
|
|
27
|
+
});
|
|
28
|
+
wrappers.push(wrapper);
|
|
29
|
+
return wrapper;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
it.each([
|
|
33
|
+
[32, '32'],
|
|
34
|
+
['32', '32'],
|
|
35
|
+
[0, 'Unlimited'],
|
|
36
|
+
['0', 'Unlimited'],
|
|
37
|
+
[null, 'Unlimited'],
|
|
38
|
+
['', 'Unlimited'],
|
|
39
|
+
[nativeFunction, 'Not set']
|
|
40
|
+
])('renders the fencer limit %j as %s', (value, expected) => {
|
|
41
|
+
const wrapper = mountCard({ EventRules: [rule('CutOffNumber', value)] });
|
|
42
|
+
expect(wrapper.vm.getMaxFencers).toBe(expected);
|
|
43
|
+
expect(wrapper.text()).not.toContain('[native code]');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('handles missing rules without crashing or inventing an elimination format', () => {
|
|
47
|
+
const wrapper = mountCard({ EventRules: undefined });
|
|
48
|
+
expect(wrapper.vm.getMaxFencers).toBe('Unlimited');
|
|
49
|
+
expect(wrapper.vm.eventSummary).toBe('Event settings not available');
|
|
50
|
+
expect(wrapper.vm.getCombinedNumberOfFencers).toBe('8 / Unlimited');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('keeps the phase, time, point and fencer summaries for nested and flat API rules', () => {
|
|
54
|
+
const EventRules = [
|
|
55
|
+
rule('PoolTime', '90'),
|
|
56
|
+
rule('DETime', '180'),
|
|
57
|
+
rule('PoolMaxPoints', '7'),
|
|
58
|
+
rule('DEMaxPoints', '12'),
|
|
59
|
+
rule('DEType', 'single'),
|
|
60
|
+
rule('DE3rdFenceOff', true),
|
|
61
|
+
rule('CutOffNumber', '32')
|
|
62
|
+
];
|
|
63
|
+
for (const event of [
|
|
64
|
+
{ EventRules },
|
|
65
|
+
{
|
|
66
|
+
EventRules: undefined,
|
|
67
|
+
rules: EventRules.map(item => ({ RuleName: item.Rules.RuleName, RuleValue: item.RuleValue }))
|
|
68
|
+
}
|
|
69
|
+
]) {
|
|
70
|
+
const wrapper = mountCard(event);
|
|
71
|
+
expect(wrapper.vm.eventSummary).toBe('Pools -> Single Elim -> 3rd Place Fence off');
|
|
72
|
+
expect(wrapper.vm.getMaxPoolPoints).toBe('7');
|
|
73
|
+
expect(wrapper.vm.getMaxDEPoints).toBe('12');
|
|
74
|
+
expect(wrapper.vm.getCombinedNumberOfFencers).toBe('8 / 32');
|
|
75
|
+
expect(wrapper.text().match(/1:30/g)).toHaveLength(2);
|
|
76
|
+
expect(wrapper.text().match(/3:00/g)).toHaveLength(2);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('renders pass-based pools and double elimination correctly', () => {
|
|
81
|
+
const wrapper = mountCard({
|
|
82
|
+
EventRules: [
|
|
83
|
+
rule('PoolNumOfPasses', '5'),
|
|
84
|
+
rule('DENumOfPasses', '7'),
|
|
85
|
+
rule('DEType', 'double'),
|
|
86
|
+
rule('DE3rdFenceOff', 'false')
|
|
87
|
+
]
|
|
88
|
+
});
|
|
89
|
+
expect(wrapper.vm.eventSummary).toBe('Pools -> Double Elim -> Tie for 3rd Place');
|
|
90
|
+
expect(wrapper.vm.getPoolBoutTimeOrPasses).toBe('5');
|
|
91
|
+
expect(wrapper.vm.getDEBoutTimeOrPasses).toBe('7');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('marks corrupted stored values as unset instead of displaying function text or guessing a format', () => {
|
|
95
|
+
const wrapper = mountCard({
|
|
96
|
+
EventRules: ['CutOffNumber', 'PoolTime', 'PoolMaxPoints', 'DETime', 'DEMaxPoints', 'DEType'].map(name =>
|
|
97
|
+
rule(name, nativeFunction)
|
|
98
|
+
)
|
|
99
|
+
});
|
|
100
|
+
expect(wrapper.text()).not.toContain('[native code]');
|
|
101
|
+
expect(wrapper.text()).not.toContain('NaN');
|
|
102
|
+
expect(wrapper.vm.eventSummary).toContain('DE format not set');
|
|
103
|
+
expect(wrapper.vm.getMaxFencers).toBe('Not set');
|
|
104
|
+
expect(wrapper.vm.getPoolBoutTimeOrPasses).toBe('Not set');
|
|
105
|
+
expect(wrapper.vm.getMaxDEPoints).toBe('Not set');
|
|
106
|
+
});
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<div class="w-full flex flex-col md:flex-row justify-between">
|
|
7
7
|
<section class="flex flex-row px-1 justify-start w-full">
|
|
8
8
|
<div class="flex flex-col w-full">
|
|
9
|
-
<div class="hidden md:flex mb-1">
|
|
9
|
+
<div class="hidden md:flex flex-wrap items-center gap-y-1 mb-1">
|
|
10
10
|
<div class="flex">
|
|
11
11
|
<BaseIcon icon-name="fa-calendar" size="xs" color="quaternary" class="mr-2" />
|
|
12
12
|
<BaseText :text="event.Date" size="xs" color="quinary" class="text-left" />
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
<BaseIcon icon-name="fa-users" size="xs" color="quaternary" class="mx-2" />
|
|
20
20
|
<BaseText :text="getMaxFencers" size="xs" color="quinary" class="text-right" />
|
|
21
21
|
</div>
|
|
22
|
+
<RegistrationStatus :status="event.RegistrationStatus ?? event.Status" class="ml-6" />
|
|
22
23
|
</div>
|
|
23
24
|
|
|
24
25
|
<BaseText
|
|
@@ -31,12 +32,17 @@
|
|
|
31
32
|
<BaseText :text="eventSummary" size="xs" color="quaternary" class="text-left" />
|
|
32
33
|
|
|
33
34
|
<div class="flex flex-col md:hidden pt-2">
|
|
34
|
-
<div class="flex">
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
<div class="flex flex-wrap items-center gap-x-4 gap-y-1">
|
|
36
|
+
<div class="flex items-center whitespace-nowrap">
|
|
37
|
+
<BaseIcon icon-name="fa-calendar" size="xs" color="quaternary" class="mr-2" />
|
|
38
|
+
<BaseText :text="event.Date" size="xs" color="quinary" class="text-left" />
|
|
39
|
+
<BaseText :text="formatStartTime(event.StartTime)" size="xs" color="quinary" class="text-left ml-1" />
|
|
40
|
+
</div>
|
|
41
|
+
<div class="flex items-center whitespace-nowrap">
|
|
42
|
+
<BaseIcon icon-name="fa-users" size="xs" color="quaternary" class="mr-2" />
|
|
43
|
+
<BaseText :text="getCombinedNumberOfFencers" size="xs" color="quinary" class="text-right" />
|
|
44
|
+
</div>
|
|
45
|
+
<RegistrationStatus :status="event.RegistrationStatus ?? event.Status" />
|
|
40
46
|
</div>
|
|
41
47
|
|
|
42
48
|
<div class="flex flex-row mt-3 w-full">
|
|
@@ -49,7 +55,7 @@
|
|
|
49
55
|
</div>
|
|
50
56
|
<div class="flex w-full">
|
|
51
57
|
<BaseIcon icon-name="fa-clock-desk" size="xs" color="quaternary" class="mr-2" />
|
|
52
|
-
<BaseText :text="
|
|
58
|
+
<BaseText :text="getPoolBoutTimeOrPasses" size="xs" color="quinary" class="text-right" />
|
|
53
59
|
</div>
|
|
54
60
|
</div>
|
|
55
61
|
</div>
|
|
@@ -151,10 +157,11 @@ import BaseButton from '../../../Molecules/Buttons/BaseButton/BaseButton.vue';
|
|
|
151
157
|
import EventStatusBox from '../../../Molecules/Boxes/EventStatusBox/EventStatusBox.vue';
|
|
152
158
|
import BaseIcon from '../../../Atoms/Icon/BaseIcon.vue';
|
|
153
159
|
import BaseModal from '../../../Molecules/Modals/BaseModal/BaseModal.vue';
|
|
160
|
+
import RegistrationStatus from '../../../Molecules/Indicators/RegistrationStatus/RegistrationStatus.vue';
|
|
154
161
|
|
|
155
162
|
export default {
|
|
156
163
|
name: 'EventCard',
|
|
157
|
-
components: { BaseModal, BaseIcon, EventStatusBox, BaseButton, BaseText },
|
|
164
|
+
components: { RegistrationStatus, BaseModal, BaseIcon, EventStatusBox, BaseButton, BaseText },
|
|
158
165
|
emits: ['manage', 'edit', 'delete'],
|
|
159
166
|
props: {
|
|
160
167
|
event: {
|
|
@@ -168,54 +175,72 @@ export default {
|
|
|
168
175
|
};
|
|
169
176
|
},
|
|
170
177
|
computed: {
|
|
178
|
+
eventRules() {
|
|
179
|
+
const rules = this.event.EventRules ?? this.event.rules;
|
|
180
|
+
return Array.isArray(rules) ? rules.filter(rule => rule && typeof rule === 'object') : [];
|
|
181
|
+
},
|
|
171
182
|
eventSummary() {
|
|
172
|
-
const rules = this.
|
|
183
|
+
const rules = this.eventRules;
|
|
184
|
+
if (rules.length === 0) return 'Event settings not available';
|
|
173
185
|
|
|
174
|
-
const poolRule = rules.find(rule =>
|
|
175
|
-
|
|
176
|
-
|
|
186
|
+
const poolRule = rules.find(rule =>
|
|
187
|
+
['PoolTime', 'PoolNumOfPasses', 'PoolMaxPoints', 'PoolSeeding'].includes(rule.Rules?.RuleName ?? rule.RuleName)
|
|
188
|
+
);
|
|
189
|
+
const deType = rules.find(rule => (rule.Rules?.RuleName ?? rule.RuleName) === 'DEType');
|
|
190
|
+
const deThirdFenceOff = rules.find(rule => (rule.Rules?.RuleName ?? rule.RuleName) === 'DE3rdFenceOff');
|
|
177
191
|
|
|
178
192
|
const poolSummary = poolRule ? 'Pools' : '';
|
|
179
|
-
const deSummary =
|
|
193
|
+
const deSummary =
|
|
194
|
+
new Map([
|
|
195
|
+
['single', 'Single Elim'],
|
|
196
|
+
['double', 'Double Elim']
|
|
197
|
+
]).get(deType?.RuleValue) ?? 'DE format not set';
|
|
180
198
|
const thirdPlaceSummary =
|
|
181
|
-
|
|
199
|
+
new Map([
|
|
200
|
+
['true', '3rd Place Fence off'],
|
|
201
|
+
['false', 'Tie for 3rd Place']
|
|
202
|
+
]).get(String(deThirdFenceOff?.RuleValue)) ?? '';
|
|
182
203
|
|
|
183
204
|
return [poolSummary, deSummary, thirdPlaceSummary].filter(Boolean).join(' -> ');
|
|
184
205
|
},
|
|
185
206
|
getMaxFencers() {
|
|
186
|
-
const maxRule = this.
|
|
187
|
-
|
|
207
|
+
const maxRule = this.eventRules.find(rule => (rule.Rules?.RuleName ?? rule.RuleName) === 'CutOffNumber');
|
|
208
|
+
const value = maxRule?.RuleValue;
|
|
209
|
+
if (value == null || value === '') return 'Unlimited';
|
|
210
|
+
const limit = Number(value);
|
|
211
|
+
if (!Number.isSafeInteger(limit) || limit < 0) return 'Not set';
|
|
212
|
+
return limit === 0 ? 'Unlimited' : String(limit);
|
|
188
213
|
},
|
|
189
214
|
getCombinedNumberOfFencers() {
|
|
190
|
-
const combined = this.event.Present + this.event.Absent;
|
|
215
|
+
const combined = Number(this.event.Present ?? 0) + Number(this.event.Absent ?? 0);
|
|
191
216
|
return combined + ' / ' + this.getMaxFencers;
|
|
192
217
|
},
|
|
193
218
|
getMaxPoolPoints() {
|
|
194
|
-
const maxRule = this.
|
|
195
|
-
return maxRule ? maxRule.RuleValue : '';
|
|
219
|
+
const maxRule = this.eventRules.find(rule => (rule.Rules?.RuleName ?? rule.RuleName) === 'PoolMaxPoints');
|
|
220
|
+
return maxRule ? this.formatRuleNumber(maxRule.RuleValue) : '';
|
|
196
221
|
},
|
|
197
222
|
getMaxDEPoints() {
|
|
198
|
-
const maxRule = this.
|
|
199
|
-
return maxRule ? maxRule.RuleValue : '';
|
|
223
|
+
const maxRule = this.eventRules.find(rule => (rule.Rules?.RuleName ?? rule.RuleName) === 'DEMaxPoints');
|
|
224
|
+
return maxRule ? this.formatRuleNumber(maxRule.RuleValue) : '';
|
|
200
225
|
},
|
|
201
226
|
getPoolBoutTimeOrPasses() {
|
|
202
|
-
const rules = this.
|
|
227
|
+
const rules = this.eventRules;
|
|
203
228
|
|
|
204
|
-
const poolTimeRule = rules.find(rule => rule.Rules.RuleName === 'PoolTime');
|
|
205
|
-
const passesRule = rules.find(rule => rule.Rules.RuleName === 'PoolNumOfPasses');
|
|
229
|
+
const poolTimeRule = rules.find(rule => (rule.Rules?.RuleName ?? rule.RuleName) === 'PoolTime');
|
|
230
|
+
const passesRule = rules.find(rule => (rule.Rules?.RuleName ?? rule.RuleName) === 'PoolNumOfPasses');
|
|
206
231
|
|
|
207
232
|
if (poolTimeRule) return this.formatTime(poolTimeRule.RuleValue);
|
|
208
|
-
if (passesRule) return passesRule.RuleValue;
|
|
233
|
+
if (passesRule) return this.formatRuleNumber(passesRule.RuleValue);
|
|
209
234
|
return '';
|
|
210
235
|
},
|
|
211
236
|
getDEBoutTimeOrPasses() {
|
|
212
|
-
const rules = this.
|
|
237
|
+
const rules = this.eventRules;
|
|
213
238
|
|
|
214
|
-
const DETimeRule = rules.find(rule => rule.Rules.RuleName === 'DETime');
|
|
215
|
-
const passesRule = rules.find(rule => rule.Rules.RuleName === 'DENumOfPasses');
|
|
239
|
+
const DETimeRule = rules.find(rule => (rule.Rules?.RuleName ?? rule.RuleName) === 'DETime');
|
|
240
|
+
const passesRule = rules.find(rule => (rule.Rules?.RuleName ?? rule.RuleName) === 'DENumOfPasses');
|
|
216
241
|
|
|
217
242
|
if (DETimeRule) return this.formatTime(DETimeRule.RuleValue);
|
|
218
|
-
if (passesRule) return passesRule.RuleValue;
|
|
243
|
+
if (passesRule) return this.formatRuleNumber(passesRule.RuleValue);
|
|
219
244
|
return '';
|
|
220
245
|
}
|
|
221
246
|
},
|
|
@@ -234,7 +259,14 @@ export default {
|
|
|
234
259
|
handleSubmit() {
|
|
235
260
|
this.$emit('delete', this.event);
|
|
236
261
|
},
|
|
262
|
+
formatRuleNumber(value) {
|
|
263
|
+
if (value == null || value === '') return '';
|
|
264
|
+
const number = Number(value);
|
|
265
|
+
return Number.isFinite(number) && number >= 0 ? String(number) : 'Not set';
|
|
266
|
+
},
|
|
237
267
|
formatTime(seconds) {
|
|
268
|
+
if (seconds == null || seconds === '' || !Number.isFinite(Number(seconds)) || Number(seconds) < 0)
|
|
269
|
+
return 'Not set';
|
|
238
270
|
const minutes = Math.floor(seconds / 60);
|
|
239
271
|
const remainingSeconds = seconds % 60;
|
|
240
272
|
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
</div>
|
|
58
58
|
|
|
59
59
|
<div class="hidden md:block mr-4">
|
|
60
|
-
<div v-if="!portraitURL" class="w-14 h-14
|
|
60
|
+
<div v-if="!portraitURL" class="w-14 h-14 rounded-xl bg-dropdownSelect flex items-center justify-center">
|
|
61
61
|
<BaseText text="" size="sm" color="neutral" weight="bold" />
|
|
62
62
|
</div>
|
|
63
63
|
<img v-if="portraitURL" :src="portraitURL" alt="Portrait" class="w-14 h-14 rounded-xl" />
|
|
@@ -7,11 +7,10 @@
|
|
|
7
7
|
<BaseText :text="event.RuleSummary" size="" color="quinary" weight="normal" class="text-xs md:text-md" />
|
|
8
8
|
<span class="hidden md:h-8"></span>
|
|
9
9
|
</section>
|
|
10
|
-
<section class="
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<div class="flex flex-row flex-nowrap">
|
|
10
|
+
<section class="flex flex-col items-start md:items-stretch">
|
|
11
|
+
<BaseText :text="fencerCountHeading" size="sm" color="quinary" class="hidden md:block text-center" />
|
|
12
|
+
<BaseText :text="registrationStatusLabel" size="xs" color="quaternary" class="md:hidden" />
|
|
13
|
+
<div class="hidden md:flex flex-row flex-nowrap">
|
|
15
14
|
<EventStatusBox :status-count="absent" status-label="ABSENT" box-color="bg-eventBoxBlue" />
|
|
16
15
|
<EventStatusBox :status-count="present" status-label="PRESENT" box-color="bg-eventBoxGreen" />
|
|
17
16
|
<EventStatusBox
|
|
@@ -35,6 +34,7 @@
|
|
|
35
34
|
import EventStatusBox from '../../../Molecules/Boxes/EventStatusBox/EventStatusBox.vue';
|
|
36
35
|
import CounterBox from '../../../Molecules/Boxes/CounterBox/CounterBox.vue';
|
|
37
36
|
import BaseText from '../../../Atoms/Text/BaseText.vue';
|
|
37
|
+
import { getRegistrationStatusLabel } from '../../../Util/registrationStatus.js';
|
|
38
38
|
|
|
39
39
|
export default {
|
|
40
40
|
name: 'AttendanceSummary',
|
|
@@ -69,6 +69,13 @@ export default {
|
|
|
69
69
|
}
|
|
70
70
|
},
|
|
71
71
|
computed: {
|
|
72
|
+
registrationStatusLabel() {
|
|
73
|
+
return getRegistrationStatusLabel(this.event.RegistrationStatus ?? this.event.Status);
|
|
74
|
+
},
|
|
75
|
+
fencerCountHeading() {
|
|
76
|
+
if (!this.registrationStatusLabel) return 'Fencing Count';
|
|
77
|
+
return `Fencing Count - ${this.registrationStatusLabel}`;
|
|
78
|
+
},
|
|
72
79
|
showRosterCounts() {
|
|
73
80
|
return this.activeCount !== '' || this.waitlistCount !== '';
|
|
74
81
|
},
|