@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.
Files changed (23) hide show
  1. package/README.md +152 -148
  2. package/dist/HemaTournamentUI-lib.es.js +4172 -4046
  3. package/dist/HemaTournamentUI-lib.umd.js +27 -27
  4. package/dist/hematournamentui-tailwind.css +11 -0
  5. package/package.json +1 -1
  6. package/src/stories/Molecules/Indicators/RegistrationStatus/RegistrationStatus.vue +30 -0
  7. package/src/stories/Organisms/Cards/EventCard/EventCard.stories.js +41 -1
  8. package/src/stories/Organisms/Cards/EventCard/EventCard.test.js +106 -0
  9. package/src/stories/Organisms/Cards/EventCard/EventCard.vue +62 -30
  10. package/src/stories/Organisms/Cards/FencerCard/FencerCard.vue +1 -1
  11. package/src/stories/Organisms/Headers/AttendanceSummary/AttendanceSummary.vue +12 -5
  12. package/src/stories/Organisms/Wizards/EditEventWizard/BasicEventInfo/BasicEventInfo.stories.js +4 -0
  13. package/src/stories/Organisms/Wizards/EditEventWizard/BasicEventInfo/BasicEventInfo.vue +249 -227
  14. package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.stories.js +4 -0
  15. package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.test.js +221 -191
  16. package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.vue +372 -366
  17. package/src/stories/Organisms/Wizards/EditEventWizard/RuleSerialization.test.js +29 -0
  18. package/src/stories/Templates/EventManagement/EventAttendance/EventAttendance.stories.js +31 -1
  19. package/src/stories/Templates/TournamentManagement/ListEvents/ListEvents.stories.js +58 -45
  20. package/src/stories/Templates/TournamentManagement/TournamentManagement.test.js +27 -2
  21. package/src/stories/Templates/TournamentManagement/TournamentPageOne/TournamentPageOne.stories.js +1 -1
  22. package/src/stories/Templates/TournamentManagement/TournamentPageOne/TournamentPageOne.vue +17 -2
  23. package/src/stories/Util/registrationStatus.js +11 -0
@@ -0,0 +1,29 @@
1
+ import { afterEach, expect, it } from 'vitest';
2
+ import { shallowMount } from '@vue/test-utils';
3
+ import EditEventWizard from './EditEventWizard.vue';
4
+
5
+ const wrappers = [];
6
+ afterEach(() => wrappers.splice(0).forEach(wrapper => wrapper.unmount()));
7
+
8
+ it.each([
9
+ ['CutOffNumber', '32', 14, '32'],
10
+ ['poolTime', '180', 1, '180'],
11
+ ['DEType', 'single', 15, 'single'],
12
+ ['DEType', 'double', 15, 'double'],
13
+ ['DEPromoteBy', 'Percent', 17, 'Percent'],
14
+ ['seedWith', 'HR - Hema Rating', 11, 'HR - Hema Rating'],
15
+ ['CutOffNumber', 0, 14, '0'],
16
+ ['thirdPlaceBout', false, 5, 'false'],
17
+ ['DEPromotionOrder', ['Win Percent', 'Indicator'], 9, 'Win Percent,Indicator'],
18
+ ['seedWith', { text: 'Manual', link: 'MN' }, 11, 'MN']
19
+ ])('serializes %s value %j without accessing inherited string methods', (field, value, ruleId, expected) => {
20
+ const wrapper = shallowMount(EditEventWizard, {
21
+ props: { event: { EventId: 12, EventName: 'Example Event', EventPrice: 0, rules: [] }, weapons: [], editMode: true }
22
+ });
23
+ wrappers.push(wrapper);
24
+ wrapper.vm.handleValueChange({ poolTimer: true, [field]: value });
25
+ wrapper.vm.handleUpdate();
26
+ const payload = wrapper.emitted('save:step')[0][0].event;
27
+ expect(payload.rules.find(rule => rule.RuleId === ruleId).RuleValue).toBe(expected);
28
+ expect(payload.rules.some(rule => rule.RuleValue.includes('[native code]'))).toBe(false);
29
+ });
@@ -110,6 +110,36 @@ export const WithWaitlist = {
110
110
  personsInTournament: [...activeRoster, ...waitlistedPersons],
111
111
  personsAll: personsAllMock,
112
112
  fencingClubs: clubsMock,
113
- event: makeEventWithCapacity(eventPersonGetEventById.event, activeRoster.length)
113
+ event: {
114
+ ...makeEventWithCapacity(eventPersonGetEventById.event, activeRoster.length),
115
+ RegistrationStatus: 'waitlist'
116
+ }
117
+ }
118
+ };
119
+
120
+ export const RegistrationOpen = {
121
+ args: {
122
+ personsInTournament: eventPersonGetEventById.persons,
123
+ personsAll: personsAllMock,
124
+ fencingClubs: clubsMock,
125
+ event: { ...eventPersonGetEventById.event, RegistrationStatus: 'open' }
126
+ }
127
+ };
128
+
129
+ export const RegistrationFull = {
130
+ args: {
131
+ personsInTournament: eventPersonGetEventById.persons,
132
+ personsAll: personsAllMock,
133
+ fencingClubs: clubsMock,
134
+ event: { ...eventPersonGetEventById.event, RegistrationStatus: 'full' }
135
+ }
136
+ };
137
+
138
+ export const RegistrationClosed = {
139
+ args: {
140
+ personsInTournament: eventPersonGetEventById.persons,
141
+ personsAll: personsAllMock,
142
+ fencingClubs: clubsMock,
143
+ event: { ...eventPersonGetEventById.event, RegistrationStatus: 'closed' }
114
144
  }
115
145
  };
@@ -1,45 +1,58 @@
1
- import ListEvents from './ListEvents.vue';
2
- import eventMockData from '../../../../mocks/eventMock.js';
3
-
4
- export default {
5
- title: 'Templates/TournamentManagement/ListEvents',
6
- component: ListEvents,
7
- tags: ['autodocs'],
8
- args: {
9
- tournamentId: 8,
10
- events: eventMockData
11
- },
12
- argTypes: {
13
- tournamentId: { control: { type: 'number' } },
14
- events: { control: { type: 'object' } },
15
- 'tab:menu-click': { action: 'tab:menu-click' },
16
- manageEvent: { action: 'manageEvent' },
17
- editEvent: { action: 'editEvent' },
18
- addEvent: { action: 'addEvent' },
19
- 'delete:Event': { action: 'delete:Event' }
20
- }
21
- };
22
-
23
- export const Default = {
24
- args: {
25
- tournamentId: 8,
26
- events: eventMockData
27
- }
28
- };
29
-
30
- export const NoEvents = {
31
- args: {
32
- tournamentId: 8,
33
- events: []
34
- }
35
- };
36
-
37
- export const CompletedEvents = {
38
- args: {
39
- tournamentId: 8,
40
- events: eventMockData.map(event => ({
41
- ...event,
42
- Status: 'completed'
43
- }))
44
- }
45
- };
1
+ import ListEvents from './ListEvents.vue';
2
+ import eventMockData from '../../../../mocks/eventMock.js';
3
+
4
+ export default {
5
+ title: 'Templates/TournamentManagement/ListEvents',
6
+ component: ListEvents,
7
+ tags: ['autodocs'],
8
+ args: {
9
+ tournamentId: 8,
10
+ events: eventMockData
11
+ },
12
+ argTypes: {
13
+ tournamentId: { control: { type: 'number' } },
14
+ events: { control: { type: 'object' } },
15
+ 'tab:menu-click': { action: 'tab:menu-click' },
16
+ manageEvent: { action: 'manageEvent' },
17
+ editEvent: { action: 'editEvent' },
18
+ addEvent: { action: 'addEvent' },
19
+ 'delete:Event': { action: 'delete:Event' }
20
+ }
21
+ };
22
+
23
+ export const Default = {
24
+ args: {
25
+ tournamentId: 8,
26
+ events: eventMockData
27
+ }
28
+ };
29
+
30
+ export const NoEvents = {
31
+ args: {
32
+ tournamentId: 8,
33
+ events: []
34
+ }
35
+ };
36
+
37
+ export const RegistrationStatuses = {
38
+ args: {
39
+ tournamentId: 8,
40
+ events: ['open', 'waitlist', 'full', 'closed'].map((RegistrationStatus, index) => ({
41
+ ...eventMockData[0],
42
+ EventId: index + 1,
43
+ EventName: ['Longsword Division 1', 'Rapier', 'Saber', 'Longsword Division 2'][index],
44
+ Status: 'registration',
45
+ RegistrationStatus
46
+ }))
47
+ }
48
+ };
49
+
50
+ export const CompletedEvents = {
51
+ args: {
52
+ tournamentId: 8,
53
+ events: eventMockData.map(event => ({
54
+ ...event,
55
+ Status: 'completed'
56
+ }))
57
+ }
58
+ };
@@ -34,6 +34,31 @@ function existingTournament(BasePrice) {
34
34
  }
35
35
 
36
36
  describe('tournament base price', () => {
37
+ it.each([
38
+ [45, '45.00'],
39
+ ['45', '45.00'],
40
+ [45.5, '45.50'],
41
+ ['45.5', '45.50']
42
+ ])('displays a saved base price of %s as %s', async (value, display) => {
43
+ const wrapper = mountTournament(existingTournament(value));
44
+ await wrapper.vm.$nextTick();
45
+ expect(priceField(wrapper).find('input').element.value).toBe(display);
46
+ });
47
+
48
+ it('formats an entered price on blur and preserves the numeric submit payload', async () => {
49
+ const wrapper = mountTournament();
50
+ await wrapper.vm.$nextTick();
51
+ wrapper.vm.updateTournament({ Name: 'Test Tournament', Address: { Name: 'Test Venue' } });
52
+ const input = priceField(wrapper).find('input');
53
+ await input.setValue('45.');
54
+ expect(input.element.value).toBe('45.');
55
+ await input.setValue('45');
56
+ await input.trigger('focusout');
57
+ expect(input.element.value).toBe('45.00');
58
+ wrapper.vm.handleSubmit();
59
+ expect(wrapper.emitted('submit:tournament')[0][0].BasePrice).toBe(45);
60
+ });
61
+
37
62
  it('retains decimal typing and submits the base price from a new tournament', async () => {
38
63
  const wrapper = mountTournament();
39
64
  await wrapper.vm.$nextTick();
@@ -61,13 +86,13 @@ describe('tournament base price', () => {
61
86
  expect(wrapper.emitted('submit:tournament')[0][0].BasePrice).toBe(80.75);
62
87
  expect(tournament.BasePrice).toBe(50.25);
63
88
  await wrapper.setProps({ tournament: existingTournament(0) });
64
- expect(priceField(wrapper).find('input').element.value).toBe('0');
89
+ expect(priceField(wrapper).find('input').element.value).toBe('0.00');
65
90
  });
66
91
 
67
92
  it.each([undefined, null, 0])('allows free tournaments with an initial price of %s', async value => {
68
93
  const wrapper = mountTournament(existingTournament(value));
69
94
  await wrapper.vm.$nextTick();
70
- expect(priceField(wrapper).find('input').element.value).toBe(value == null ? '' : '0');
95
+ expect(priceField(wrapper).find('input').element.value).toBe(value == null ? '' : '0.00');
71
96
  expect(priceField(wrapper).find('input').attributes('placeholder')).toBe('0.00');
72
97
  await priceField(wrapper).find('input').setValue('');
73
98
  wrapper.vm.handleSubmit();
@@ -89,7 +89,7 @@ export const EmptyTournament = {
89
89
 
90
90
  export const EditMode = {
91
91
  args: {
92
- tournament: { ...mockTournaments[0], BasePrice: 75.5 },
92
+ tournament: { ...mockTournaments[0], BasePrice: 45 },
93
93
  locations: mockLocation,
94
94
  persons: mockPersons,
95
95
  validation: validTournamentFields,
@@ -51,7 +51,8 @@
51
51
  :invalid="isFieldInvalid('BasePrice')"
52
52
  :title="getValidationLabel('BasePrice', 'Base Price')"
53
53
  placeholder="0.00"
54
- @update:value="emitUpdate('BasePrice', $event)"
54
+ @update:value="handleBasePriceUpdate"
55
+ @focusout="handleBasePriceBlur"
55
56
  class="mb-4"
56
57
  />
57
58
 
@@ -223,6 +224,9 @@ export default {
223
224
  TitledInput,
224
225
  TextBoxEditor
225
226
  },
227
+ data() {
228
+ return { basePriceDraft: null };
229
+ },
226
230
  computed: {
227
231
  tournamentImage() {
228
232
  if (this.tournament.Images && this.tournament.Images.length > 0) {
@@ -234,7 +238,11 @@ export default {
234
238
  return this.tournament?.Name || '';
235
239
  },
236
240
  basePrice() {
237
- return this.tournament?.BasePrice ?? '';
241
+ const price = this.tournament?.BasePrice ?? '';
242
+ if (price === '' || price === this.basePriceDraft) return price;
243
+ const amount = Number(price);
244
+ if (!Number.isFinite(amount)) return price;
245
+ return amount.toFixed(2);
238
246
  },
239
247
  addressName() {
240
248
  return this.tournament?.Address?.Name || '';
@@ -257,6 +265,13 @@ export default {
257
265
  }
258
266
  },
259
267
  methods: {
268
+ handleBasePriceUpdate(value) {
269
+ this.basePriceDraft = value;
270
+ this.emitUpdate('BasePrice', value);
271
+ },
272
+ handleBasePriceBlur() {
273
+ this.basePriceDraft = null;
274
+ },
260
275
  handleNewImageUrl(url) {
261
276
  this.$emit('update:image', { ImageLink: url });
262
277
  },
@@ -0,0 +1,11 @@
1
+ const statusLabels = new Map([
2
+ ['open', 'Open'],
3
+ ['waitlist', 'Waitlist'],
4
+ ['full', 'Full'],
5
+ ['closed', 'Closed']
6
+ ]);
7
+
8
+ export function getRegistrationStatusLabel(status) {
9
+ const value = typeof status === 'string' ? status.trim().toLowerCase() : '';
10
+ return statusLabels.get(value) ?? '';
11
+ }