@dcrackel/hematournamentui 1.0.712 → 1.0.714

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 (107) hide show
  1. package/.prettierignore +7 -7
  2. package/.storybook/main.js +3 -1
  3. package/README.md +42 -0
  4. package/dist/HemaTournamentUI-lib.es.js +6856 -6243
  5. package/dist/HemaTournamentUI-lib.umd.js +27 -27
  6. package/dist/hematournamentui-tailwind.css +79 -9
  7. package/package.json +96 -96
  8. package/prettier.config.js +16 -16
  9. package/src/index.js +43 -42
  10. package/src/main.js +1 -1
  11. package/src/mocks/getDEBouts-TableOf128.js +37 -37
  12. package/src/mocks/getDEBouts-TableOf64.js +35 -35
  13. package/src/stories/Atoms/Input/BaseInput.test.js +97 -97
  14. package/src/stories/Atoms/Input/BaseInput.vue +149 -149
  15. package/src/stories/Molecules/Indicators/MissingHemaRatingsId/MissingHemaRatingsId.stories.js +23 -23
  16. package/src/stories/Molecules/Indicators/MissingHemaRatingsId/MissingHemaRatingsId.vue +35 -35
  17. package/src/stories/Molecules/Indicators/RegistrationStatus/RegistrationStatus.vue +30 -30
  18. package/src/stories/Molecules/Menus/HoverActions/HoverActions.test.js +138 -0
  19. package/src/stories/Molecules/Menus/HoverActions/HoverActions.vue +212 -0
  20. package/src/stories/Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.stories.js +49 -0
  21. package/src/stories/Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.test.js +129 -0
  22. package/src/stories/Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.vue +165 -0
  23. package/src/stories/Molecules/Modals/DirectorModal/DirectorModal.stories.js +6 -0
  24. package/src/stories/Molecules/Modals/DirectorModal/DirectorModal.test.js +18 -18
  25. package/src/stories/Molecules/Modals/DirectorModal/DirectorModal.vue +1 -1
  26. package/src/stories/Organisms/Cards/BoutCard/BoutCard.vue +5 -1
  27. package/src/stories/Organisms/Cards/BoutCard/Variants/BoutCardActive/BoutCardActive.vue +19 -10
  28. package/src/stories/Organisms/Cards/BoutCard/Variants/BoutCardInactive/BoutCardInactive.vue +11 -7
  29. package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.stories.js +42 -33
  30. package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.test.js +76 -45
  31. package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.vue +122 -103
  32. package/src/stories/Organisms/Cards/Director/DirectorCard.test.js +82 -0
  33. package/src/stories/Organisms/Cards/Director/DirectorCard.vue +32 -51
  34. package/src/stories/Organisms/Cards/EditBout/EditBout.test.js +51 -51
  35. package/src/stories/Organisms/Cards/EventCard/EventCard.test.js +106 -106
  36. package/src/stories/Organisms/Cards/FencerCard/FencerCard.vue +298 -298
  37. package/src/stories/Organisms/Cards/TableauBoutCard/TableauBoutCard.stories.js +77 -60
  38. package/src/stories/Organisms/Cards/TableauBoutCard/TableauBoutCard.test.js +144 -143
  39. package/src/stories/Organisms/Cards/TableauBoutCard/TableauBoutCard.vue +289 -256
  40. package/src/stories/Organisms/Cards/TableauFencerCard/TableauFencerCard.stories.js +69 -69
  41. package/src/stories/Organisms/Cards/TableauFencerCard/TableauFencerCard.vue +80 -80
  42. package/src/stories/Organisms/Cards/WaitlistFencerCard/WaitlistFencerCard.vue +153 -153
  43. package/src/stories/Organisms/ComplexInputs/DropDown/DropDownMenu.vue +4 -0
  44. package/src/stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.stories.js +31 -31
  45. package/src/stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.test.js +109 -109
  46. package/src/stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.vue +192 -192
  47. package/src/stories/Organisms/Containers/Tableau/Tableau.stories.js +1 -0
  48. package/src/stories/Organisms/Containers/Tableau/Tableau.test.js +247 -204
  49. package/src/stories/Organisms/Containers/Tableau/Tableau.vue +89 -15
  50. package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.test.js +51 -29
  51. package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.vue +63 -46
  52. package/src/stories/Organisms/Containers/Tableau/components/TableauSvgOverlay.test.js +21 -21
  53. package/src/stories/Organisms/Containers/Tableau/components/TableauSvgOverlay.vue +39 -39
  54. package/src/stories/Organisms/Containers/Tableau/layout/TableauLayoutEngine.js +145 -145
  55. package/src/stories/Organisms/Containers/Tableau/layout/TableauLayoutEngine.test.js +64 -64
  56. package/src/stories/Organisms/Containers/Tableau/plannedAssignments.js +36 -57
  57. package/src/stories/Organisms/Containers/Tableau/plannedAssignments.test.js +101 -65
  58. package/src/stories/Organisms/Containers/TableauColumn/TableauColumn.vue +14 -1
  59. package/src/stories/Organisms/Wizards/EditEventWizard/BasicEventInfo/BasicEventInfo.stories.js +52 -52
  60. package/src/stories/Organisms/Wizards/EditEventWizard/BasicEventInfo/BasicEventInfo.vue +10 -3
  61. package/src/stories/Organisms/Wizards/EditEventWizard/DirectElimination/DirectElimination.vue +226 -226
  62. package/src/stories/Organisms/Wizards/EditEventWizard/DirectEliminationPromotion/DirectEliminationPromotion.vue +204 -204
  63. package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.stories.js +137 -137
  64. package/src/stories/Organisms/Wizards/EditEventWizard/EventCustomization/EventCustomization.vue +172 -172
  65. package/src/stories/Organisms/Wizards/EditEventWizard/PoolConfiguration/PoolConfiguration.vue +216 -216
  66. package/src/stories/Organisms/Wizards/EditEventWizard/PriceFields.test.js +79 -79
  67. package/src/stories/Organisms/Wizards/EditEventWizard/RuleSerialization.test.js +29 -29
  68. package/src/stories/Organisms/Wizards/EditEventWizard/WeaponSelection.test.js +138 -0
  69. package/src/stories/Templates/EventManagement/Bracket/Bracket.stories.js +149 -9
  70. package/src/stories/Templates/EventManagement/Bracket/Bracket.test.js +54 -2
  71. package/src/stories/Templates/EventManagement/Bracket/Bracket.vue +77 -8
  72. package/src/stories/Templates/EventManagement/Bracket/BracketDirectorAssignment.test.js +242 -0
  73. package/src/stories/Templates/EventManagement/Bracket/BracketMobile.test.js +131 -0
  74. package/src/stories/Templates/EventManagement/Bracket/BracketPlannedSuggestions.js +50 -39
  75. package/src/stories/Templates/EventManagement/Bracket/BracketStories.test.js +201 -0
  76. package/src/stories/Templates/EventManagement/Bracket/BracketStoryAssignments.js +93 -0
  77. package/src/stories/Templates/EventManagement/Bracket/BracketStoryAssignments.test.js +152 -0
  78. package/src/stories/Templates/EventManagement/Bracket/BracketStoryHarness.js +39 -39
  79. package/src/stories/Templates/EventManagement/Bracket/BracketStoryHarness.test.js +79 -79
  80. package/src/stories/Templates/EventManagement/EditEventInfo/EditEventInfo.stories.js +93 -93
  81. package/src/stories/Templates/EventManagement/EditEventInfo/EditEventInfo.vue +91 -91
  82. package/src/stories/Templates/EventManagement/PoolLive/PoolLive.test.js +121 -121
  83. package/src/stories/Templates/Menus/EditEventsTopMenu/EditEventsTopMenu.stories.js +9 -2
  84. package/src/stories/Templates/Menus/EditEventsTopMenu/EditEventsTopMenu.vue +5 -4
  85. package/src/stories/Templates/PersonManagement/Admin/Administration/Administration.stories.js +81 -81
  86. package/src/stories/Templates/PersonManagement/Admin/Administration/Administration.test.js +39 -39
  87. package/src/stories/Templates/PersonManagement/Admin/Administration/Administration.vue +101 -101
  88. package/src/stories/Templates/PersonManagement/Admin/FencerAdmin/FencerAdmin.stories.js +71 -71
  89. package/src/stories/Templates/PersonManagement/Admin/FencerAdmin/FencerAdmin.vue +86 -86
  90. package/src/stories/Templates/PersonManagement/Admin/MergeAccounts/MergeAccounts.stories.js +86 -86
  91. package/src/stories/Templates/PersonManagement/Admin/MergeAccounts/MergeAccounts.vue +247 -247
  92. package/src/stories/Templates/PersonManagement/Basic/BasicEdit.vue +170 -170
  93. package/src/stories/Templates/PersonManagement/PersonManagement.stories.js +156 -156
  94. package/src/stories/Templates/PersonManagement/PersonManagement.test.js +329 -329
  95. package/src/stories/Templates/PersonManagement/PersonManagement.vue +329 -329
  96. package/src/stories/Templates/PersonManagement/PersonManagementDraft.js +42 -42
  97. package/src/stories/Templates/TournamentManagement/TournamentManagement.stories.js +84 -84
  98. package/src/stories/Templates/TournamentManagement/TournamentManagement.test.js +114 -114
  99. package/src/stories/Templates/TournamentManagement/TournamentManagement.vue +363 -363
  100. package/src/stories/Templates/TournamentManagement/TournamentPageOne/TournamentPageOne.stories.js +116 -116
  101. package/src/stories/Templates/TournamentManagement/TournamentPageOne/TournamentPageOne.vue +307 -307
  102. package/src/stories/Util/Validations/PersonManagementValidations.js +45 -45
  103. package/src/stories/Util/Validations/PriceValidation.js +6 -6
  104. package/src/stories/Util/boutDirectorAssignment.js +46 -0
  105. package/src/stories/Util/bracketDirectors.js +86 -0
  106. package/src/stories/Util/registrationStatus.js +11 -11
  107. package/src/stories/Util/tabs.js +19 -47
@@ -1,363 +1,363 @@
1
- <template>
2
- <div class="bg-neutral w-full">
3
- <EditEventsTopMenu :tabs="filteredTabs" :currentTab="currentTab" @tabMenuClick="handleTab" />
4
- <TournamentPageOne
5
- v-if="currentTab === 'Basic'"
6
- :tournament="localTournament"
7
- :locations="locations"
8
- :persons="personsList"
9
- :validation="validation"
10
- :imageServer="imageServer"
11
- :uploadImageName="uploadImageName"
12
- :uploadServer="uploadServer"
13
- :isNewTournament="isNewTournament"
14
- :userLevel="userLevel"
15
- @add:newAddress="handleAddNewAddress"
16
- @add:newPerson="handleAddNewPerson"
17
- @update:address="handleAddressUpdate"
18
- @update:image="handleNewImageUrl"
19
- @update:tournament="updateTournament"
20
- />
21
-
22
- <TournamentPageTwo
23
- v-if="currentTab === 'Hosting'"
24
- :tournament="localTournament"
25
- :isNewTournament="isNewTournament"
26
- @update:tournament="updateTournament"
27
- @go:results-import="handleTab('ResultsImport')"
28
- />
29
-
30
- <div v-if="isNewTournament" class="flex flex-row justify-center h-12 mt-4">
31
- <BaseButton
32
- v-if="currentTab === 'Basic'"
33
- class="w-1/2 ml-1 h-12"
34
- color="neutral"
35
- label="Cancel"
36
- size="sm"
37
- type="primary"
38
- @buttonClick="handleCancel"
39
- />
40
- <BaseButton
41
- v-if="currentTab === 'Hosting'"
42
- class="w-1/2 ml-1 h-12"
43
- color="neutral"
44
- label="Back"
45
- size="sm"
46
- type="primary"
47
- @buttonClick="handleBack"
48
- />
49
-
50
- <BaseButton
51
- v-if="currentTab === 'Basic'"
52
- class="w-1/2 mr-1 transition-all duration-300 ease-in-out h-12"
53
- color="neutral"
54
- label="Next"
55
- :selected="true"
56
- size="sm"
57
- type="primary"
58
- @buttonClick="handleNext"
59
- />
60
- <BaseButton
61
- v-if="currentTab === 'Hosting'"
62
- class="w-1/2 mr-1 transition-all duration-300 ease-in-out h-12"
63
- color="neutral"
64
- :label="submitButtonLabel"
65
- :selected="true"
66
- size="sm"
67
- type="primary"
68
- @buttonClick="handleSubmit"
69
- />
70
- </div>
71
-
72
- <div v-if="!isNewTournament" class="flex flex-row justify-center h-12 mt-4">
73
- <BaseButton
74
- class="w-1/2 mr-1 transition-all duration-300 ease-in-out h-12"
75
- color="neutral"
76
- :label="submitButtonLabel"
77
- :selected="true"
78
- size="sm"
79
- type="primary"
80
- @buttonClick="handleSubmit"
81
- />
82
- </div>
83
- </div>
84
- </template>
85
-
86
- <script>
87
- import TournamentPageOne from './TournamentPageOne/TournamentPageOne.vue';
88
- import BaseButton from '../../Molecules/Buttons/BaseButton/BaseButton.vue';
89
- import EditEventsTopMenu from '../Menus/EditEventsTopMenu/EditEventsTopMenu.vue';
90
- import { tournamentTabs } from '../../Util/tabs.js';
91
- import TournamentPageTwo from './TournamentPageTwo/TournamentPageTwo.vue';
92
- import { validateField } from '../../Util/Validations/TournamentFieldsValidations.js';
93
-
94
- export default {
95
- title: 'TournamentManagement',
96
- components: {
97
- TournamentPageTwo,
98
- EditEventsTopMenu,
99
- BaseButton,
100
- TournamentPageOne
101
- },
102
- emits: [
103
- 'submit:tournament',
104
- 'cancel',
105
- 'add:newAddress',
106
- 'add:newPerson',
107
- 'update:address',
108
- 'update:image',
109
- 'tab:menu-click'
110
- ],
111
- props: {
112
- tournament: {
113
- type: Object,
114
- required: false
115
- },
116
- locations: {
117
- type: Array,
118
- required: true
119
- },
120
- persons: {
121
- type: [Array, Object],
122
- required: false,
123
- default: () => []
124
- },
125
- uploadImageName: {
126
- type: String,
127
- default: 'default-image'
128
- },
129
- uploadServer: {
130
- type: String,
131
- default: ''
132
- },
133
- imageServer: {
134
- type: String,
135
- default: ''
136
- },
137
- showTab: {
138
- type: String,
139
- default: 'Basic'
140
- },
141
- userLevel: {
142
- type: String,
143
- default: 'Club'
144
- },
145
- submitButtonLabel: {
146
- type: String,
147
- default: 'Save'
148
- }
149
- },
150
- data() {
151
- return {
152
- currentTab: this.showTab,
153
- localTournament: this.createNewTournamentDraft(),
154
- isNewTournament: true,
155
- validation: {
156
- Name: { invalid: false, message: '' },
157
- BasePrice: { invalid: false, message: '' },
158
- Address: { invalid: false, message: '' },
159
- StartDate: { invalid: false, message: '' },
160
- TotalDays: { invalid: false, message: '' },
161
- RegistrationCutOff: { invalid: false, message: '' },
162
- Description: { invalid: false, message: '' }
163
- }
164
- };
165
- },
166
- watch: {
167
- tournament: {
168
- handler: 'initializeTournamentDraft'
169
- },
170
- showTab(tab) {
171
- this.currentTab = tab;
172
- },
173
- 'localTournament.StartDate': 'setEndDate',
174
- 'localTournament.TotalDays': 'setEndDate'
175
- },
176
- mounted() {
177
- this.initializeTournamentDraft();
178
- },
179
- computed: {
180
- filteredTabs() {
181
- return this.isNewTournament ? tournamentTabs().filter(tab => tab.label !== 'Events') : tournamentTabs();
182
- },
183
- personsList() {
184
- return Array.isArray(this.persons) ? this.persons : this.persons ? [this.persons] : [];
185
- }
186
- },
187
- methods: {
188
- tournamentTabs,
189
- handleTab(tab) {
190
- this.currentTab = tab;
191
- this.$emit('tab:menu-click', tab);
192
- },
193
- initializeTournamentDraft() {
194
- if (!this.tournament || Object.keys(this.tournament).length === 0 || !this.tournament.Name) {
195
- this.isNewTournament = true;
196
- this.localTournament = this.createNewTournamentDraft();
197
- return;
198
- }
199
-
200
- this.isNewTournament = false;
201
- this.localTournament = this.createExistingTournamentDraft(this.tournament);
202
- this.setEndDate();
203
- },
204
- createNewTournamentDraft() {
205
- const today = new Date().toISOString().slice(0, 10);
206
-
207
- return {
208
- TournamentId: 0,
209
- Name: '',
210
- BasePrice: '',
211
- Address: '',
212
- TotalDays: 1,
213
- StartDate: today,
214
- RegistrationCutOff: today,
215
- Description: '',
216
- HostedOnM2: false,
217
- ResultsEnabled: false,
218
- Visibility: 'Open',
219
- WaiverScope: 'None'
220
- };
221
- },
222
- createExistingTournamentDraft(tournament) {
223
- const tournamentDraft = this.cloneTournamentData(tournament);
224
- const startDate = new Date(tournamentDraft.StartDate);
225
- const endDate = new Date(tournamentDraft.EndDate);
226
- const TotalDays =
227
- !isNaN(startDate.getTime()) && !isNaN(endDate.getTime())
228
- ? Math.max(1, (endDate - startDate) / (1000 * 60 * 60 * 24) + 1)
229
- : 1;
230
-
231
- return {
232
- ...tournamentDraft,
233
- BasePrice: tournamentDraft.BasePrice ?? '',
234
- TotalDays,
235
- StartDate: this.formatDateOnly(tournamentDraft.StartDate),
236
- EndDate: this.formatDateOnly(tournamentDraft.EndDate),
237
- RegistrationCutOff: this.formatDateOnly(tournamentDraft.RegistrationCutOff),
238
- Visibility: tournamentDraft.Visibility ?? 'Open',
239
- WaiverScope: tournamentDraft.WaiverScope ?? 'None',
240
- ResultsEnabled: tournamentDraft.ResultsEnabled ?? false
241
- };
242
- },
243
- cloneTournamentData(tournament) {
244
- return JSON.parse(JSON.stringify(tournament));
245
- },
246
- handleAddNewAddress(newAddress) {
247
- this.updateTournament({ AddressId: 0, Address: newAddress });
248
- this.$emit('add:newAddress', newAddress);
249
- },
250
- handleAddNewPerson(personName) {
251
- this.$emit('add:newPerson', personName);
252
- },
253
- handleAddressUpdate(updatedAddress) {
254
- if (updatedAddress) {
255
- this.updateTournament({
256
- AddressId: updatedAddress.AddressId,
257
- Address: updatedAddress
258
- });
259
- this.$emit('update:address', updatedAddress);
260
- this.validation.Address = validateField('Address', updatedAddress);
261
- return;
262
- }
263
-
264
- this.updateTournament({ AddressId: 0, Address: '' });
265
- },
266
- validateAllFields() {
267
- const fieldsToValidate = ['Name', 'BasePrice', 'TotalDays', 'Address'];
268
- fieldsToValidate.forEach(field => {
269
- this.validation[field] = validateField(field, this.localTournament[field]);
270
- });
271
- return !Object.values(this.validation).some(field => field.invalid);
272
- },
273
- handleNewImageUrl(imageUpdate) {
274
- this.updateTournament(imageUpdate);
275
- this.$emit('update:image', imageUpdate);
276
- },
277
- updateTournament(updatedData) {
278
- const normalizedData = this.normalizeTournamentUpdate(updatedData);
279
- this.localTournament = { ...this.localTournament, ...normalizedData };
280
- Object.keys(normalizedData).forEach(field => {
281
- if (this.validation[field]) {
282
- this.validation[field] = validateField(field, normalizedData[field]);
283
- }
284
- });
285
- },
286
- normalizeTournamentUpdate(updatedData) {
287
- const normalizedData = { ...updatedData };
288
- const dateFields = ['StartDate', 'EndDate', 'RegistrationCutOff'];
289
-
290
- dateFields.forEach(field => {
291
- if (field in normalizedData) {
292
- normalizedData[field] = this.formatDateOnly(normalizedData[field]);
293
- }
294
- });
295
-
296
- return normalizedData;
297
- },
298
- formatDateOnly(dateValue) {
299
- if (!dateValue) return '';
300
- if (typeof dateValue === 'string') return dateValue.slice(0, 10);
301
-
302
- const date = new Date(dateValue);
303
- if (isNaN(date.getTime())) return '';
304
-
305
- const year = date.getFullYear();
306
- const month = String(date.getMonth() + 1).padStart(2, '0');
307
- const day = String(date.getDate()).padStart(2, '0');
308
- return `${year}-${month}-${day}`;
309
- },
310
- addDaysInclusiveYMD(startYmd, totalDays) {
311
- const [y, m, d] = startYmd.split('-').map(Number);
312
- const start = new Date(Date.UTC(y, m - 1, d));
313
- const end = new Date(start);
314
- end.setUTCDate(end.getUTCDate() + Math.max(1, Number(totalDays) || 1) - 1);
315
- return end.toISOString().slice(0, 10); // 'YYYY-MM-DD'
316
- },
317
- setEndDate() {
318
- if (!this.localTournament?.StartDate) return;
319
- const startDate = this.formatDateOnly(this.localTournament.StartDate);
320
- if (!startDate) return;
321
-
322
- const days = Math.max(1, Number(this.localTournament.TotalDays) || 1);
323
- this.localTournament.EndDate = this.addDaysInclusiveYMD(startDate, days);
324
- },
325
- handleCancel() {
326
- this.$emit('cancel');
327
- },
328
- transition(action) {
329
- const table = {
330
- Basic: {
331
- NEXT: { to: 'Hosting', guard: () => this.validateAllFields() },
332
- BACK: { to: 'Hosting' }
333
- },
334
- Hosting: {
335
- NEXT: { to: 'Basic' },
336
- BACK: { to: 'Basic' }
337
- }
338
- };
339
-
340
- const state = table[this.currentTab];
341
- if (!state) return;
342
-
343
- const rule = state[action];
344
- if (!rule) return;
345
-
346
- if (rule.guard && !rule.guard()) return;
347
-
348
- this.currentTab = typeof rule.to === 'function' ? rule.to(this.currentTab) : rule.to;
349
- },
350
- handleNext() {
351
- this.transition('NEXT');
352
- },
353
- handleBack() {
354
- this.transition('BACK');
355
- },
356
- handleSubmit() {
357
- if (this.validateAllFields()) {
358
- this.$emit('submit:tournament', { ...this.localTournament, BasePrice: Number(this.localTournament.BasePrice) });
359
- }
360
- }
361
- }
362
- };
363
- </script>
1
+ <template>
2
+ <div class="bg-neutral w-full">
3
+ <EditEventsTopMenu :tabs="filteredTabs" :currentTab="currentTab" @tabMenuClick="handleTab" />
4
+ <TournamentPageOne
5
+ v-if="currentTab === 'Basic'"
6
+ :tournament="localTournament"
7
+ :locations="locations"
8
+ :persons="personsList"
9
+ :validation="validation"
10
+ :imageServer="imageServer"
11
+ :uploadImageName="uploadImageName"
12
+ :uploadServer="uploadServer"
13
+ :isNewTournament="isNewTournament"
14
+ :userLevel="userLevel"
15
+ @add:newAddress="handleAddNewAddress"
16
+ @add:newPerson="handleAddNewPerson"
17
+ @update:address="handleAddressUpdate"
18
+ @update:image="handleNewImageUrl"
19
+ @update:tournament="updateTournament"
20
+ />
21
+
22
+ <TournamentPageTwo
23
+ v-if="currentTab === 'Hosting'"
24
+ :tournament="localTournament"
25
+ :isNewTournament="isNewTournament"
26
+ @update:tournament="updateTournament"
27
+ @go:results-import="handleTab('ResultsImport')"
28
+ />
29
+
30
+ <div v-if="isNewTournament" class="flex flex-row justify-center h-12 mt-4">
31
+ <BaseButton
32
+ v-if="currentTab === 'Basic'"
33
+ class="w-1/2 ml-1 h-12"
34
+ color="neutral"
35
+ label="Cancel"
36
+ size="sm"
37
+ type="primary"
38
+ @buttonClick="handleCancel"
39
+ />
40
+ <BaseButton
41
+ v-if="currentTab === 'Hosting'"
42
+ class="w-1/2 ml-1 h-12"
43
+ color="neutral"
44
+ label="Back"
45
+ size="sm"
46
+ type="primary"
47
+ @buttonClick="handleBack"
48
+ />
49
+
50
+ <BaseButton
51
+ v-if="currentTab === 'Basic'"
52
+ class="w-1/2 mr-1 transition-all duration-300 ease-in-out h-12"
53
+ color="neutral"
54
+ label="Next"
55
+ :selected="true"
56
+ size="sm"
57
+ type="primary"
58
+ @buttonClick="handleNext"
59
+ />
60
+ <BaseButton
61
+ v-if="currentTab === 'Hosting'"
62
+ class="w-1/2 mr-1 transition-all duration-300 ease-in-out h-12"
63
+ color="neutral"
64
+ :label="submitButtonLabel"
65
+ :selected="true"
66
+ size="sm"
67
+ type="primary"
68
+ @buttonClick="handleSubmit"
69
+ />
70
+ </div>
71
+
72
+ <div v-if="!isNewTournament" class="flex flex-row justify-center h-12 mt-4">
73
+ <BaseButton
74
+ class="w-1/2 mr-1 transition-all duration-300 ease-in-out h-12"
75
+ color="neutral"
76
+ :label="submitButtonLabel"
77
+ :selected="true"
78
+ size="sm"
79
+ type="primary"
80
+ @buttonClick="handleSubmit"
81
+ />
82
+ </div>
83
+ </div>
84
+ </template>
85
+
86
+ <script>
87
+ import TournamentPageOne from './TournamentPageOne/TournamentPageOne.vue';
88
+ import BaseButton from '../../Molecules/Buttons/BaseButton/BaseButton.vue';
89
+ import EditEventsTopMenu from '../Menus/EditEventsTopMenu/EditEventsTopMenu.vue';
90
+ import { tournamentTabs } from '../../Util/tabs.js';
91
+ import TournamentPageTwo from './TournamentPageTwo/TournamentPageTwo.vue';
92
+ import { validateField } from '../../Util/Validations/TournamentFieldsValidations.js';
93
+
94
+ export default {
95
+ title: 'TournamentManagement',
96
+ components: {
97
+ TournamentPageTwo,
98
+ EditEventsTopMenu,
99
+ BaseButton,
100
+ TournamentPageOne
101
+ },
102
+ emits: [
103
+ 'submit:tournament',
104
+ 'cancel',
105
+ 'add:newAddress',
106
+ 'add:newPerson',
107
+ 'update:address',
108
+ 'update:image',
109
+ 'tab:menu-click'
110
+ ],
111
+ props: {
112
+ tournament: {
113
+ type: Object,
114
+ required: false
115
+ },
116
+ locations: {
117
+ type: Array,
118
+ required: true
119
+ },
120
+ persons: {
121
+ type: [Array, Object],
122
+ required: false,
123
+ default: () => []
124
+ },
125
+ uploadImageName: {
126
+ type: String,
127
+ default: 'default-image'
128
+ },
129
+ uploadServer: {
130
+ type: String,
131
+ default: ''
132
+ },
133
+ imageServer: {
134
+ type: String,
135
+ default: ''
136
+ },
137
+ showTab: {
138
+ type: String,
139
+ default: 'Basic'
140
+ },
141
+ userLevel: {
142
+ type: String,
143
+ default: 'Club'
144
+ },
145
+ submitButtonLabel: {
146
+ type: String,
147
+ default: 'Save'
148
+ }
149
+ },
150
+ data() {
151
+ return {
152
+ currentTab: this.showTab,
153
+ localTournament: this.createNewTournamentDraft(),
154
+ isNewTournament: true,
155
+ validation: {
156
+ Name: { invalid: false, message: '' },
157
+ BasePrice: { invalid: false, message: '' },
158
+ Address: { invalid: false, message: '' },
159
+ StartDate: { invalid: false, message: '' },
160
+ TotalDays: { invalid: false, message: '' },
161
+ RegistrationCutOff: { invalid: false, message: '' },
162
+ Description: { invalid: false, message: '' }
163
+ }
164
+ };
165
+ },
166
+ watch: {
167
+ tournament: {
168
+ handler: 'initializeTournamentDraft'
169
+ },
170
+ showTab(tab) {
171
+ this.currentTab = tab;
172
+ },
173
+ 'localTournament.StartDate': 'setEndDate',
174
+ 'localTournament.TotalDays': 'setEndDate'
175
+ },
176
+ mounted() {
177
+ this.initializeTournamentDraft();
178
+ },
179
+ computed: {
180
+ filteredTabs() {
181
+ return this.isNewTournament ? tournamentTabs().filter(tab => tab.label !== 'Events') : tournamentTabs();
182
+ },
183
+ personsList() {
184
+ return Array.isArray(this.persons) ? this.persons : this.persons ? [this.persons] : [];
185
+ }
186
+ },
187
+ methods: {
188
+ tournamentTabs,
189
+ handleTab(tab) {
190
+ this.currentTab = tab;
191
+ this.$emit('tab:menu-click', tab);
192
+ },
193
+ initializeTournamentDraft() {
194
+ if (!this.tournament || Object.keys(this.tournament).length === 0 || !this.tournament.Name) {
195
+ this.isNewTournament = true;
196
+ this.localTournament = this.createNewTournamentDraft();
197
+ return;
198
+ }
199
+
200
+ this.isNewTournament = false;
201
+ this.localTournament = this.createExistingTournamentDraft(this.tournament);
202
+ this.setEndDate();
203
+ },
204
+ createNewTournamentDraft() {
205
+ const today = new Date().toISOString().slice(0, 10);
206
+
207
+ return {
208
+ TournamentId: 0,
209
+ Name: '',
210
+ BasePrice: '',
211
+ Address: '',
212
+ TotalDays: 1,
213
+ StartDate: today,
214
+ RegistrationCutOff: today,
215
+ Description: '',
216
+ HostedOnM2: false,
217
+ ResultsEnabled: false,
218
+ Visibility: 'Open',
219
+ WaiverScope: 'None'
220
+ };
221
+ },
222
+ createExistingTournamentDraft(tournament) {
223
+ const tournamentDraft = this.cloneTournamentData(tournament);
224
+ const startDate = new Date(tournamentDraft.StartDate);
225
+ const endDate = new Date(tournamentDraft.EndDate);
226
+ const TotalDays =
227
+ !isNaN(startDate.getTime()) && !isNaN(endDate.getTime())
228
+ ? Math.max(1, (endDate - startDate) / (1000 * 60 * 60 * 24) + 1)
229
+ : 1;
230
+
231
+ return {
232
+ ...tournamentDraft,
233
+ BasePrice: tournamentDraft.BasePrice ?? '',
234
+ TotalDays,
235
+ StartDate: this.formatDateOnly(tournamentDraft.StartDate),
236
+ EndDate: this.formatDateOnly(tournamentDraft.EndDate),
237
+ RegistrationCutOff: this.formatDateOnly(tournamentDraft.RegistrationCutOff),
238
+ Visibility: tournamentDraft.Visibility ?? 'Open',
239
+ WaiverScope: tournamentDraft.WaiverScope ?? 'None',
240
+ ResultsEnabled: tournamentDraft.ResultsEnabled ?? false
241
+ };
242
+ },
243
+ cloneTournamentData(tournament) {
244
+ return JSON.parse(JSON.stringify(tournament));
245
+ },
246
+ handleAddNewAddress(newAddress) {
247
+ this.updateTournament({ AddressId: 0, Address: newAddress });
248
+ this.$emit('add:newAddress', newAddress);
249
+ },
250
+ handleAddNewPerson(personName) {
251
+ this.$emit('add:newPerson', personName);
252
+ },
253
+ handleAddressUpdate(updatedAddress) {
254
+ if (updatedAddress) {
255
+ this.updateTournament({
256
+ AddressId: updatedAddress.AddressId,
257
+ Address: updatedAddress
258
+ });
259
+ this.$emit('update:address', updatedAddress);
260
+ this.validation.Address = validateField('Address', updatedAddress);
261
+ return;
262
+ }
263
+
264
+ this.updateTournament({ AddressId: 0, Address: '' });
265
+ },
266
+ validateAllFields() {
267
+ const fieldsToValidate = ['Name', 'BasePrice', 'TotalDays', 'Address'];
268
+ fieldsToValidate.forEach(field => {
269
+ this.validation[field] = validateField(field, this.localTournament[field]);
270
+ });
271
+ return !Object.values(this.validation).some(field => field.invalid);
272
+ },
273
+ handleNewImageUrl(imageUpdate) {
274
+ this.updateTournament(imageUpdate);
275
+ this.$emit('update:image', imageUpdate);
276
+ },
277
+ updateTournament(updatedData) {
278
+ const normalizedData = this.normalizeTournamentUpdate(updatedData);
279
+ this.localTournament = { ...this.localTournament, ...normalizedData };
280
+ Object.keys(normalizedData).forEach(field => {
281
+ if (this.validation[field]) {
282
+ this.validation[field] = validateField(field, normalizedData[field]);
283
+ }
284
+ });
285
+ },
286
+ normalizeTournamentUpdate(updatedData) {
287
+ const normalizedData = { ...updatedData };
288
+ const dateFields = ['StartDate', 'EndDate', 'RegistrationCutOff'];
289
+
290
+ dateFields.forEach(field => {
291
+ if (field in normalizedData) {
292
+ normalizedData[field] = this.formatDateOnly(normalizedData[field]);
293
+ }
294
+ });
295
+
296
+ return normalizedData;
297
+ },
298
+ formatDateOnly(dateValue) {
299
+ if (!dateValue) return '';
300
+ if (typeof dateValue === 'string') return dateValue.slice(0, 10);
301
+
302
+ const date = new Date(dateValue);
303
+ if (isNaN(date.getTime())) return '';
304
+
305
+ const year = date.getFullYear();
306
+ const month = String(date.getMonth() + 1).padStart(2, '0');
307
+ const day = String(date.getDate()).padStart(2, '0');
308
+ return `${year}-${month}-${day}`;
309
+ },
310
+ addDaysInclusiveYMD(startYmd, totalDays) {
311
+ const [y, m, d] = startYmd.split('-').map(Number);
312
+ const start = new Date(Date.UTC(y, m - 1, d));
313
+ const end = new Date(start);
314
+ end.setUTCDate(end.getUTCDate() + Math.max(1, Number(totalDays) || 1) - 1);
315
+ return end.toISOString().slice(0, 10); // 'YYYY-MM-DD'
316
+ },
317
+ setEndDate() {
318
+ if (!this.localTournament?.StartDate) return;
319
+ const startDate = this.formatDateOnly(this.localTournament.StartDate);
320
+ if (!startDate) return;
321
+
322
+ const days = Math.max(1, Number(this.localTournament.TotalDays) || 1);
323
+ this.localTournament.EndDate = this.addDaysInclusiveYMD(startDate, days);
324
+ },
325
+ handleCancel() {
326
+ this.$emit('cancel');
327
+ },
328
+ transition(action) {
329
+ const table = {
330
+ Basic: {
331
+ NEXT: { to: 'Hosting', guard: () => this.validateAllFields() },
332
+ BACK: { to: 'Hosting' }
333
+ },
334
+ Hosting: {
335
+ NEXT: { to: 'Basic' },
336
+ BACK: { to: 'Basic' }
337
+ }
338
+ };
339
+
340
+ const state = table[this.currentTab];
341
+ if (!state) return;
342
+
343
+ const rule = state[action];
344
+ if (!rule) return;
345
+
346
+ if (rule.guard && !rule.guard()) return;
347
+
348
+ this.currentTab = typeof rule.to === 'function' ? rule.to(this.currentTab) : rule.to;
349
+ },
350
+ handleNext() {
351
+ this.transition('NEXT');
352
+ },
353
+ handleBack() {
354
+ this.transition('BACK');
355
+ },
356
+ handleSubmit() {
357
+ if (this.validateAllFields()) {
358
+ this.$emit('submit:tournament', { ...this.localTournament, BasePrice: Number(this.localTournament.BasePrice) });
359
+ }
360
+ }
361
+ }
362
+ };
363
+ </script>