@dcrackel/hematournamentui 1.0.708 → 1.0.710

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 (38) hide show
  1. package/README.md +148 -106
  2. package/dist/HemaTournamentUI-lib.es.js +6678 -6196
  3. package/dist/HemaTournamentUI-lib.umd.js +27 -27
  4. package/dist/hematournamentui-tailwind.css +16 -6
  5. package/package.json +1 -1
  6. package/src/stories/Atoms/Input/BaseInput.test.js +97 -85
  7. package/src/stories/Atoms/Input/BaseInput.vue +149 -150
  8. package/src/stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.stories.js +31 -20
  9. package/src/stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.test.js +109 -0
  10. package/src/stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.vue +192 -133
  11. package/src/stories/Organisms/Wizards/EditEventWizard/BasicEventInfo/BasicEventInfo.stories.js +48 -41
  12. package/src/stories/Organisms/Wizards/EditEventWizard/BasicEventInfo/BasicEventInfo.vue +227 -206
  13. package/src/stories/Organisms/Wizards/EditEventWizard/DirectElimination/DirectElimination.vue +226 -225
  14. package/src/stories/Organisms/Wizards/EditEventWizard/DirectEliminationPromotion/DirectEliminationPromotion.vue +204 -204
  15. package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.stories.js +133 -91
  16. package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.test.js +191 -0
  17. package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.vue +366 -309
  18. package/src/stories/Organisms/Wizards/EditEventWizard/EventCustomization/EventCustomization.vue +172 -171
  19. package/src/stories/Organisms/Wizards/EditEventWizard/PoolConfiguration/PoolConfiguration.vue +216 -215
  20. package/src/stories/Organisms/Wizards/EditEventWizard/PriceFields.test.js +79 -0
  21. package/src/stories/Templates/EventManagement/EditEventInfo/EditEventInfo.stories.js +93 -52
  22. package/src/stories/Templates/EventManagement/EditEventInfo/EditEventInfo.vue +91 -75
  23. package/src/stories/Templates/PersonManagement/Admin/Administration/Administration.stories.js +14 -0
  24. package/src/stories/Templates/PersonManagement/Admin/Administration/Administration.test.js +39 -0
  25. package/src/stories/Templates/PersonManagement/Admin/Administration/Administration.vue +13 -3
  26. package/src/stories/Templates/PersonManagement/Basic/BasicEdit.vue +170 -172
  27. package/src/stories/Templates/PersonManagement/PersonManagement.stories.js +156 -103
  28. package/src/stories/Templates/PersonManagement/PersonManagement.test.js +329 -0
  29. package/src/stories/Templates/PersonManagement/PersonManagement.vue +329 -213
  30. package/src/stories/Templates/PersonManagement/PersonManagementDraft.js +42 -0
  31. package/src/stories/Templates/TournamentManagement/TournamentManagement.stories.js +84 -83
  32. package/src/stories/Templates/TournamentManagement/TournamentManagement.test.js +89 -0
  33. package/src/stories/Templates/TournamentManagement/TournamentManagement.vue +363 -360
  34. package/src/stories/Templates/TournamentManagement/TournamentPageOne/TournamentPageOne.stories.js +116 -113
  35. package/src/stories/Templates/TournamentManagement/TournamentPageOne/TournamentPageOne.vue +292 -280
  36. package/src/stories/Util/Validations/PersonManagementValidations.js +45 -29
  37. package/src/stories/Util/Validations/PriceValidation.js +6 -0
  38. package/src/stories/Util/Validations/TournamentFieldsValidations.js +4 -0
@@ -1,360 +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
- Address: { invalid: false, message: '' },
158
- StartDate: { invalid: false, message: '' },
159
- TotalDays: { invalid: false, message: '' },
160
- RegistrationCutOff: { invalid: false, message: '' },
161
- Description: { invalid: false, message: '' }
162
- }
163
- };
164
- },
165
- watch: {
166
- tournament: {
167
- handler: 'initializeTournamentDraft'
168
- },
169
- showTab(tab) {
170
- this.currentTab = tab;
171
- },
172
- 'localTournament.StartDate': 'setEndDate',
173
- 'localTournament.TotalDays': 'setEndDate'
174
- },
175
- mounted() {
176
- this.initializeTournamentDraft();
177
- },
178
- computed: {
179
- filteredTabs() {
180
- return this.isNewTournament ? tournamentTabs().filter(tab => tab.label !== 'Events') : tournamentTabs();
181
- },
182
- personsList() {
183
- return Array.isArray(this.persons) ? this.persons : this.persons ? [this.persons] : [];
184
- }
185
- },
186
- methods: {
187
- tournamentTabs,
188
- handleTab(tab) {
189
- this.currentTab = tab;
190
- this.$emit('tab:menu-click', tab);
191
- },
192
- initializeTournamentDraft() {
193
- if (!this.tournament || Object.keys(this.tournament).length === 0 || !this.tournament.Name) {
194
- this.isNewTournament = true;
195
- this.localTournament = this.createNewTournamentDraft();
196
- return;
197
- }
198
-
199
- this.isNewTournament = false;
200
- this.localTournament = this.createExistingTournamentDraft(this.tournament);
201
- this.setEndDate();
202
- },
203
- createNewTournamentDraft() {
204
- const today = new Date().toISOString().slice(0, 10);
205
-
206
- return {
207
- TournamentId: 0,
208
- Name: '',
209
- Address: '',
210
- TotalDays: 1,
211
- StartDate: today,
212
- RegistrationCutOff: today,
213
- Description: '',
214
- HostedOnM2: false,
215
- ResultsEnabled: false,
216
- Visibility: 'Open',
217
- WaiverScope: 'None'
218
- };
219
- },
220
- createExistingTournamentDraft(tournament) {
221
- const tournamentDraft = this.cloneTournamentData(tournament);
222
- const startDate = new Date(tournamentDraft.StartDate);
223
- const endDate = new Date(tournamentDraft.EndDate);
224
- const TotalDays =
225
- !isNaN(startDate.getTime()) && !isNaN(endDate.getTime())
226
- ? Math.max(1, (endDate - startDate) / (1000 * 60 * 60 * 24) + 1)
227
- : 1;
228
-
229
- return {
230
- ...tournamentDraft,
231
- TotalDays,
232
- StartDate: this.formatDateOnly(tournamentDraft.StartDate),
233
- EndDate: this.formatDateOnly(tournamentDraft.EndDate),
234
- RegistrationCutOff: this.formatDateOnly(tournamentDraft.RegistrationCutOff),
235
- Visibility: tournamentDraft.Visibility ?? 'Open',
236
- WaiverScope: tournamentDraft.WaiverScope ?? 'None',
237
- ResultsEnabled: tournamentDraft.ResultsEnabled ?? false
238
- };
239
- },
240
- cloneTournamentData(tournament) {
241
- return JSON.parse(JSON.stringify(tournament));
242
- },
243
- handleAddNewAddress(newAddress) {
244
- this.updateTournament({ AddressId: 0, Address: newAddress });
245
- this.$emit('add:newAddress', newAddress);
246
- },
247
- handleAddNewPerson(personName) {
248
- this.$emit('add:newPerson', personName);
249
- },
250
- handleAddressUpdate(updatedAddress) {
251
- if (updatedAddress) {
252
- this.updateTournament({
253
- AddressId: updatedAddress.AddressId,
254
- Address: updatedAddress
255
- });
256
- this.$emit('update:address', updatedAddress);
257
- this.validation.Address = validateField('Address', updatedAddress);
258
- return;
259
- }
260
-
261
- this.updateTournament({ AddressId: 0, Address: '' });
262
- },
263
- validateAllFields() {
264
- const fieldsToValidate = ['Name', 'TotalDays', 'Address'];
265
- fieldsToValidate.forEach(field => {
266
- this.validation[field] = validateField(field, this.localTournament[field]);
267
- });
268
- return !Object.values(this.validation).some(field => field.invalid);
269
- },
270
- handleNewImageUrl(imageUpdate) {
271
- this.updateTournament(imageUpdate);
272
- this.$emit('update:image', imageUpdate);
273
- },
274
- updateTournament(updatedData) {
275
- const normalizedData = this.normalizeTournamentUpdate(updatedData);
276
- this.localTournament = { ...this.localTournament, ...normalizedData };
277
- Object.keys(normalizedData).forEach(field => {
278
- if (this.validation[field]) {
279
- this.validation[field] = validateField(field, normalizedData[field]);
280
- }
281
- });
282
- },
283
- normalizeTournamentUpdate(updatedData) {
284
- const normalizedData = { ...updatedData };
285
- const dateFields = ['StartDate', 'EndDate', 'RegistrationCutOff'];
286
-
287
- dateFields.forEach(field => {
288
- if (field in normalizedData) {
289
- normalizedData[field] = this.formatDateOnly(normalizedData[field]);
290
- }
291
- });
292
-
293
- return normalizedData;
294
- },
295
- formatDateOnly(dateValue) {
296
- if (!dateValue) return '';
297
- if (typeof dateValue === 'string') return dateValue.slice(0, 10);
298
-
299
- const date = new Date(dateValue);
300
- if (isNaN(date.getTime())) return '';
301
-
302
- const year = date.getFullYear();
303
- const month = String(date.getMonth() + 1).padStart(2, '0');
304
- const day = String(date.getDate()).padStart(2, '0');
305
- return `${year}-${month}-${day}`;
306
- },
307
- addDaysInclusiveYMD(startYmd, totalDays) {
308
- const [y, m, d] = startYmd.split('-').map(Number);
309
- const start = new Date(Date.UTC(y, m - 1, d));
310
- const end = new Date(start);
311
- end.setUTCDate(end.getUTCDate() + Math.max(1, Number(totalDays) || 1) - 1);
312
- return end.toISOString().slice(0, 10); // 'YYYY-MM-DD'
313
- },
314
- setEndDate() {
315
- if (!this.localTournament?.StartDate) return;
316
- const startDate = this.formatDateOnly(this.localTournament.StartDate);
317
- if (!startDate) return;
318
-
319
- const days = Math.max(1, Number(this.localTournament.TotalDays) || 1);
320
- this.localTournament.EndDate = this.addDaysInclusiveYMD(startDate, days);
321
- },
322
- handleCancel() {
323
- this.$emit('cancel');
324
- },
325
- transition(action) {
326
- const table = {
327
- Basic: {
328
- NEXT: { to: 'Hosting', guard: () => this.validateAllFields() },
329
- BACK: { to: 'Hosting' }
330
- },
331
- Hosting: {
332
- NEXT: { to: 'Basic' },
333
- BACK: { to: 'Basic' }
334
- }
335
- };
336
-
337
- const state = table[this.currentTab];
338
- if (!state) return;
339
-
340
- const rule = state[action];
341
- if (!rule) return;
342
-
343
- if (rule.guard && !rule.guard()) return;
344
-
345
- this.currentTab = typeof rule.to === 'function' ? rule.to(this.currentTab) : rule.to;
346
- },
347
- handleNext() {
348
- this.transition('NEXT');
349
- },
350
- handleBack() {
351
- this.transition('BACK');
352
- },
353
- handleSubmit() {
354
- if (this.validateAllFields()) {
355
- this.$emit('submit:tournament', this.localTournament);
356
- }
357
- }
358
- }
359
- };
360
- </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>