@dcrackel/hematournamentui 1.0.711 → 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.
@@ -1,366 +1,372 @@
1
- <template>
2
- <div class="p-4" :class="{ 'max-h-screen overflow-y-auto': !editMode }">
3
- <fieldset :key="draftVersion" :disabled="saving" :inert="saving" :aria-busy="saving" class="min-w-0 border-0 p-0">
4
- <BasicEventInfo
5
- v-if="currentStep === 1"
6
- :eventConfig="eventConfig"
7
- :weapons="weapons"
8
- :validation="validation"
9
- :steps="steps"
10
- :currentStep="currentStep - 1"
11
- :nextButtonLabel="nextButtonLabel"
12
- :userLevel="userLevel"
13
- @update="handleValueChange"
14
- @update:back="handleCancel"
15
- @update:next="handleUpdate"
16
- />
17
- <EventCustomization
18
- v-if="currentStep === 2"
19
- :eventConfig="eventConfig"
20
- :steps="steps"
21
- :currentStep="currentStep - 1"
22
- :nextButtonLabel="nextButtonLabel"
23
- @update="handleValueChange"
24
- @update:back="handleBack"
25
- @update:next="handleUpdate"
26
- />
27
- <PoolConfiguration
28
- v-if="currentStep === 3"
29
- :eventConfig="eventConfig"
30
- :seedWithOptions="seedWithOptions"
31
- :validation="validation"
32
- :steps="steps"
33
- :currentStep="currentStep - 1"
34
- :nextButtonLabel="nextButtonLabel"
35
- @update="handleValueChange"
36
- @update:back="handleBack"
37
- @update:next="handleUpdate"
38
- />
39
- <DirectEliminationPromotion
40
- v-if="currentStep === 4"
41
- :eventConfig="eventConfig"
42
- :validation="validation"
43
- :steps="steps"
44
- :currentStep="currentStep - 1"
45
- :nextButtonLabel="nextButtonLabel"
46
- @update="handleValueChange"
47
- @update:DEPromotionType="handleDEPromotionType"
48
- @update:back="handleBack"
49
- @update:next="handleUpdate"
50
- />
51
- <DirectElimination
52
- v-if="currentStep === 5"
53
- :eventConfig="eventConfig"
54
- :validation="validation"
55
- :steps="steps"
56
- :currentStep="currentStep - 1"
57
- :nextButtonLabel="nextButtonLabel"
58
- :DEFormats="DEFormats"
59
- @update="handleValueChange"
60
- @update:back="handleBack"
61
- @update:submit="handleSubmit"
62
- />
63
- </fieldset>
64
- <p role="status" aria-live="polite" aria-atomic="true" class="mt-3 min-h-5 text-sm text-primaryHighlight">
65
- {{ saving ? 'Saving changes…' : saveNotice }}
66
- </p>
67
- <p v-if="saveError" role="alert" class="mt-2 text-sm text-alarm">{{ saveError }}</p>
68
- </div>
69
- </template>
70
-
71
- <script>
72
- import BasicEventInfo from './BasicEventInfo/BasicEventInfo.vue';
73
- import { validatePrice } from '../../../Util/Validations/PriceValidation.js';
74
- import PoolConfiguration from './PoolConfiguration/PoolConfiguration.vue';
75
- import DirectElimination from './DirectElimination/DirectElimination.vue';
76
- import BaseButton from '../../../Molecules/Buttons/BaseButton/BaseButton.vue';
77
- import StepIndicator from '../../../Molecules/StepIndicator/BaseStepIndicator/StepIndicator.vue';
78
- import DirectEliminationPromotion from './DirectEliminationPromotion/DirectEliminationPromotion.vue';
79
- import EventCustomization from '../../..//Organisms/Wizards/EditEventWizard/EventCustomization/EventCustomization.vue';
80
-
81
- const ruleValueIsTrue = (rule, fallbackField) =>
82
- rule.RuleValue === 'true' || rule.RuleValue === true || rule[fallbackField] === true;
83
-
84
- const rulePatchCreators = {
85
- 1: rule => ({ poolTime: rule.RuleValue, poolTimer: true }),
86
- 2: rule => ({ poolMaxPoints: rule.RuleValue, poolMax: true }),
87
- 3: rule => ({ DETime: rule.RuleValue, DETimer: true }),
88
- 4: rule => ({ DEMaxPoints: rule.RuleValue, DEMax: true }),
89
- 5: rule => ({ thirdPlaceBout: ruleValueIsTrue(rule, 'thirdPlaceBout') }),
90
- 6: rule => ({ boutCount: rule.RuleValue, boutCounter: true }),
91
- 7: rule => ({ DEBoutCount: rule.RuleValue, DEBoutCounter: true }),
92
- 9: rule => ({ DEPromotionOrder: rule.RuleValue.split(',') }),
93
- 10: rule => ({ DEPromotionAmount: rule.RuleValue }),
94
- 11: rule => ({ seedWith: rule.RuleValue }),
95
- 14: rule => ({ CutOffNumber: rule.RuleValue }),
96
- 15: rule => ({ DEType: rule.RuleValue }),
97
- 16: rule => ({ PoolScoreDirection: ruleValueIsTrue(rule, 'PoolScoreDirection') }),
98
- 17: rule => ({ DEPromoteBy: rule.RuleValue }),
99
- 19: rule => ({ CountHandshots: ruleValueIsTrue(rule, 'CountHandshots') }),
100
- 20: rule => ({ CountDoubles: ruleValueIsTrue(rule, 'CountDoubles') }),
101
- 21: rule => ({ PenaltiesReset: ruleValueIsTrue(rule, 'PenaltiesReset') }),
102
- 22: rule => ({ IncludeInRatings: ruleValueIsTrue(rule, 'IncludeInRatings') })
103
- };
104
-
105
- const createRulePatch = rule => (rulePatchCreators[rule.RuleId] || (() => ({})))(rule);
106
-
107
- const applyRulesToEventConfig = (eventConfig, rules) =>
108
- rules.reduce((currentConfig, rule) => ({ ...currentConfig, ...createRulePatch(rule) }), { ...eventConfig });
109
-
110
- const cloneEvent = event => ({
111
- ...(event || {}),
112
- rules: Array.isArray(event?.rules) ? event.rules.map(rule => ({ ...rule })) : []
113
- });
114
-
115
- const createEventConfig = event => ({
116
- EventName: event.EventName,
117
- EventPrice: event.EventPrice ?? '',
118
- Date: event.startDate ?? event.Date,
119
- StartTime: event.startTime ?? event.StartTime,
120
- NumberOfRings: event.NumberOfRings,
121
- WeaponId: event.WeaponId,
122
- seedWith: 'M2 - MeyerSquared',
123
- CutOffNumber: undefined,
124
- boutCounter: false,
125
- boutCount: 5,
126
- poolTimer: false,
127
- poolMax: false,
128
- DEMax: false,
129
- thirdPlaceBout: false,
130
- poolTime: 180,
131
- poolMaxPoints: 7,
132
- PoolScoreDirection: true,
133
- DETimer: false,
134
- DETime: 180,
135
- DEBoutCounter: false,
136
- DEBoutCount: 7,
137
- DEMaxPoints: 12,
138
- DEPromotionAmount: '100%',
139
- DEPromotionOrder: ['Win Percent', 'Win Count', 'Indicator', 'Points Scored'],
140
- DEPromoteBy: 'Percent',
141
- DEType: 'single',
142
- CountHandshots: false,
143
- CountDoubles: false,
144
- PenaltiesReset: true,
145
- IncludeInRatings: event.IncludeInRatings ?? true
146
- });
147
-
148
- export default {
149
- name: 'EditEventWizard',
150
- components: {
151
- EventCustomization,
152
- DirectEliminationPromotion,
153
- StepIndicator,
154
- BasicEventInfo,
155
- PoolConfiguration,
156
- DirectElimination,
157
- BaseButton
158
- },
159
- emits: ['update:submit', 'update:cancel', 'save:step', 'navigate:step', 'update:dirty'],
160
- props: {
161
- editMode: { type: Boolean, default: false },
162
- step: { type: Number, default: 1 },
163
- saving: { type: Boolean, default: false },
164
- saveError: { type: String, default: '' },
165
- saveNotice: { type: String, default: '' },
166
-
167
- event: {
168
- type: Object
169
- },
170
- weapons: {
171
- type: Array,
172
- required: true
173
- },
174
- saveButtonLabel: {
175
- type: String,
176
- default: 'Save'
177
- },
178
- DEFormats: {
179
- type: Array,
180
- default: () => [
181
- { link: 1, text: 'Single Elimination' },
182
- { link: 2, text: 'Double Elimination' }
183
- ]
184
- },
185
- userLevel: {
186
- type: String,
187
- default: 'Club'
188
- }
189
- },
190
- data() {
191
- return {
192
- currentStep: this.step,
193
- localEvent: cloneEvent(this.event),
194
- hasClickedNext: false,
195
- validation: {
196
- EventName: { invalid: false, message: '' },
197
- EventPrice: { invalid: false, message: '' }
198
- },
199
- eventConfig: createEventConfig(this.event),
200
- savedDraft: '',
201
- draftVersion: 0,
202
- seedWithOptions: [
203
- { text: 'M2 - MeyerSquared', link: 'M2' },
204
- { text: 'HR - Hema Rating', link: 'HR' },
205
- { text: 'Manual', link: 'MN' }
206
- ],
207
- steps: [
208
- { name: 'Basic Info', icon: 'fa-circle-info' },
209
- { name: 'Customization', icon: 'fa-star' },
210
- { name: 'Pool Setup', icon: 'fa-table-columns' },
211
- { name: 'DE Promotion', icon: 'fa-list' },
212
- { name: 'Direct Elimination', icon: 'fa-diagram-sankey' }
213
- ]
214
- };
215
- },
216
- computed: {
217
- nextButtonLabel() {
218
- if (!this.editMode) return this.currentStep === 5 ? 'Submit' : 'Next';
219
- return this.currentStep === 5 ? 'Save' : 'Save and Continue';
220
- }
221
- },
222
- mounted() {
223
- this.resetDraft();
224
- },
225
- watch: {
226
- event() {
227
- this.resetDraft();
228
- },
229
- step(value) {
230
- if (Number.isInteger(value) && value >= 1 && value <= this.steps.length) this.currentStep = value;
231
- }
232
- },
233
- methods: {
234
- resetDraft() {
235
- this.localEvent = cloneEvent(this.event);
236
- this.eventConfig = applyRulesToEventConfig(createEventConfig(this.event), this.localEvent.rules);
237
- if (typeof this.event.IncludeInRatings === 'boolean')
238
- this.eventConfig.IncludeInRatings = this.event.IncludeInRatings;
239
- this.savedDraft = JSON.stringify(this.eventConfig);
240
- this.draftVersion += 1;
241
- this.publishDirtyState();
242
- },
243
- publishDirtyState() {
244
- if (this.editMode) this.$emit('update:dirty', JSON.stringify(this.eventConfig) !== this.savedDraft);
245
- },
246
- handleValueChange(change) {
247
- if (this.saving) return;
248
- this.eventConfig = { ...this.eventConfig, ...change };
249
- this.localEvent = { ...this.localEvent, ...change };
250
- this.publishDirtyState();
251
- if (this.hasClickedNext) this.validateAll();
252
- },
253
- handleDEPromotionType(value) {
254
- this.handleValueChange({ DEPromoteBy: value });
255
- },
256
- validateAll() {
257
- let isValid = true;
258
- this.validation.EventPrice = validatePrice(this.eventConfig.EventPrice);
259
- const forbiddenChars = /[\/\\'"=:*<>|?&!@#$%^(){}[\]]/g;
260
-
261
- if (!this.eventConfig.EventName || this.eventConfig.EventName.trim().length < 4) {
262
- this.validation.EventName = {
263
- invalid: true,
264
- message: 'Event Name must be at least 3 characters long.'
265
- };
266
- isValid = false;
267
- return isValid;
268
- }
269
-
270
- if (forbiddenChars.test(this.eventConfig.EventName)) {
271
- this.validation.EventName = {
272
- invalid: true,
273
- message: 'Event Name contains invalid characters.'
274
- };
275
- isValid = false;
276
- return isValid;
277
- }
278
-
279
- this.validation.EventName = { invalid: false, message: '' };
280
- return !this.validation.EventPrice.invalid;
281
- },
282
- handleUpdate(eventConfig = {}) {
283
- if (this.saving) return;
284
- this.hasClickedNext = true;
285
- if (!this.editMode) this.eventConfig = { ...this.eventConfig, ...eventConfig };
286
- if (!this.validateAll()) return;
287
- if (this.editMode) {
288
- this.requestStepSave();
289
- return;
290
- }
291
- this.currentStep += 1;
292
- },
293
- handleBack() {
294
- if (this.saving) return;
295
- if (this.editMode) {
296
- this.$emit('navigate:step', this.currentStep - 1);
297
- return;
298
- }
299
- this.currentStep -= 1;
300
- },
301
- convertEventConfigToRules(eventConfig) {
302
- const mapping = {
303
- poolTime: { RuleId: 1, RuleName: 'PoolTime', Context: 'pool', dependsOn: 'poolTimer' },
304
- poolMaxPoints: { RuleId: 2, RuleName: 'PoolMaxPoints', Context: 'pool', dependsOn: 'poolMax' },
305
- DETime: { RuleId: 3, RuleName: 'DETime', Context: 'de', dependsOn: 'DETimer' },
306
- DEMaxPoints: { RuleId: 4, RuleName: 'DEMaxPoints', Context: 'de', dependsOn: 'DEMax' },
307
- thirdPlaceBout: { RuleId: 5, RuleName: 'DE3rdFenceOff', Context: 'de' },
308
- boutCount: { RuleId: 6, RuleName: 'PoolNumOfPasses', Context: 'pool', dependsOn: 'boutCounter' },
309
- DEBoutCount: { RuleId: 7, RuleName: 'DENumOfPasses', Context: 'de', dependsOn: 'DEBoutCounter' },
310
- DEPromotionAmount: { RuleId: 10, RuleName: 'DENumOfPromotion', Context: 'de' },
311
- DEPromotionOrder: { RuleId: 9, RuleName: 'DEPromotionOrder', Context: 'de' },
312
- seedWith: { RuleId: 11, RuleName: 'PoolSeeding', Context: 'pool' },
313
- CutOffNumber: { RuleId: 14, RuleName: 'CutOffNumber', Context: 'event' },
314
- DEType: { RuleId: 15, RuleName: 'DEType', Context: 'de' },
315
- PoolScoreDirection: { RuleId: 16, RuleName: 'PoolScoreDirection', Context: 'de' },
316
- DEPromoteBy: { RuleId: 17, RuleName: 'DEPromoteBy', Context: 'de' },
317
- CountHandshots: { RuleId: 19, RuleName: 'CountHandshots', Context: 'event' },
318
- CountDoubles: { RuleId: 20, RuleName: 'CountDoubles', Context: 'event' },
319
- PenaltiesReset: { RuleId: 21, RuleName: 'PenaltiesReset', Context: 'event' },
320
- IncludeInRatings: { RuleId: 22, RuleName: 'IncludeInRatings', Context: 'event', onlyWhenExisting: true }
321
- };
322
-
323
- return Object.entries(eventConfig)
324
- .filter(([key, value]) => mapping[key] && value !== undefined)
325
- .filter(
326
- ([key]) =>
327
- !mapping[key].onlyWhenExisting ||
328
- this.localEvent.rules.some(rule => Number(rule.RuleId) === mapping[key].RuleId)
329
- )
330
- .filter(([key]) => {
331
- const dependency = mapping[key].dependsOn;
332
- return !dependency || eventConfig[dependency];
333
- })
334
- .map(([key, value]) => ({
335
- RuleId: mapping[key].RuleId,
336
- RuleName: mapping[key].RuleName,
337
- RuleValue: Array.isArray(value) ? value.join(',') : String(value?.link ?? value),
338
- Context: mapping[key].Context
339
- }));
340
- },
341
- buildEventPayload() {
342
- return {
343
- ...cloneEvent(this.localEvent),
344
- ...this.eventConfig,
345
- EventPrice: Number(this.eventConfig.EventPrice),
346
- rules: this.convertEventConfigToRules(this.eventConfig)
347
- };
348
- },
349
- requestStepSave() {
350
- this.$emit('save:step', { event: this.buildEventPayload(), step: this.currentStep });
351
- },
352
- handleSubmit() {
353
- if (this.saving || !this.validateAll()) return;
354
- if (this.editMode) {
355
- this.requestStepSave();
356
- return;
357
- }
358
- this.$emit('update:submit', this.buildEventPayload());
359
- },
360
- handleCancel() {
361
- if (this.saving) return;
362
- this.$emit('update:cancel');
363
- }
364
- }
365
- };
366
- </script>
1
+ <template>
2
+ <div class="p-4" :class="{ 'max-h-screen overflow-y-auto': !editMode }">
3
+ <fieldset :key="draftVersion" :disabled="saving" :inert="saving" :aria-busy="saving" class="min-w-0 border-0 p-0">
4
+ <BasicEventInfo
5
+ v-if="currentStep === 1"
6
+ :eventConfig="eventConfig"
7
+ :weapons="weapons"
8
+ :validation="validation"
9
+ :steps="steps"
10
+ :currentStep="currentStep - 1"
11
+ :nextButtonLabel="nextButtonLabel"
12
+ :userLevel="userLevel"
13
+ @update="handleValueChange"
14
+ @update:back="handleCancel"
15
+ @update:next="handleUpdate"
16
+ />
17
+ <EventCustomization
18
+ v-if="currentStep === 2"
19
+ :eventConfig="eventConfig"
20
+ :steps="steps"
21
+ :currentStep="currentStep - 1"
22
+ :nextButtonLabel="nextButtonLabel"
23
+ @update="handleValueChange"
24
+ @update:back="handleBack"
25
+ @update:next="handleUpdate"
26
+ />
27
+ <PoolConfiguration
28
+ v-if="currentStep === 3"
29
+ :eventConfig="eventConfig"
30
+ :seedWithOptions="seedWithOptions"
31
+ :validation="validation"
32
+ :steps="steps"
33
+ :currentStep="currentStep - 1"
34
+ :nextButtonLabel="nextButtonLabel"
35
+ @update="handleValueChange"
36
+ @update:back="handleBack"
37
+ @update:next="handleUpdate"
38
+ />
39
+ <DirectEliminationPromotion
40
+ v-if="currentStep === 4"
41
+ :eventConfig="eventConfig"
42
+ :validation="validation"
43
+ :steps="steps"
44
+ :currentStep="currentStep - 1"
45
+ :nextButtonLabel="nextButtonLabel"
46
+ @update="handleValueChange"
47
+ @update:DEPromotionType="handleDEPromotionType"
48
+ @update:back="handleBack"
49
+ @update:next="handleUpdate"
50
+ />
51
+ <DirectElimination
52
+ v-if="currentStep === 5"
53
+ :eventConfig="eventConfig"
54
+ :validation="validation"
55
+ :steps="steps"
56
+ :currentStep="currentStep - 1"
57
+ :nextButtonLabel="nextButtonLabel"
58
+ :DEFormats="DEFormats"
59
+ @update="handleValueChange"
60
+ @update:back="handleBack"
61
+ @update:submit="handleSubmit"
62
+ />
63
+ </fieldset>
64
+ <p role="status" aria-live="polite" aria-atomic="true" class="mt-3 min-h-5 text-sm text-primaryHighlight">
65
+ {{ saving ? 'Saving changes…' : saveNotice }}
66
+ </p>
67
+ <p v-if="saveError" role="alert" class="mt-2 text-sm text-alarm">{{ saveError }}</p>
68
+ </div>
69
+ </template>
70
+
71
+ <script>
72
+ import BasicEventInfo from './BasicEventInfo/BasicEventInfo.vue';
73
+ import { validatePrice } from '../../../Util/Validations/PriceValidation.js';
74
+ import PoolConfiguration from './PoolConfiguration/PoolConfiguration.vue';
75
+ import DirectElimination from './DirectElimination/DirectElimination.vue';
76
+ import BaseButton from '../../../Molecules/Buttons/BaseButton/BaseButton.vue';
77
+ import StepIndicator from '../../../Molecules/StepIndicator/BaseStepIndicator/StepIndicator.vue';
78
+ import DirectEliminationPromotion from './DirectEliminationPromotion/DirectEliminationPromotion.vue';
79
+ import EventCustomization from '../../..//Organisms/Wizards/EditEventWizard/EventCustomization/EventCustomization.vue';
80
+
81
+ const ruleValueIsTrue = (rule, fallbackField) =>
82
+ rule.RuleValue === 'true' || rule.RuleValue === true || rule[fallbackField] === true;
83
+
84
+ const rulePatchCreators = {
85
+ 1: rule => ({ poolTime: rule.RuleValue, poolTimer: true }),
86
+ 2: rule => ({ poolMaxPoints: rule.RuleValue, poolMax: true }),
87
+ 3: rule => ({ DETime: rule.RuleValue, DETimer: true }),
88
+ 4: rule => ({ DEMaxPoints: rule.RuleValue, DEMax: true }),
89
+ 5: rule => ({ thirdPlaceBout: ruleValueIsTrue(rule, 'thirdPlaceBout') }),
90
+ 6: rule => ({ boutCount: rule.RuleValue, boutCounter: true }),
91
+ 7: rule => ({ DEBoutCount: rule.RuleValue, DEBoutCounter: true }),
92
+ 9: rule => ({ DEPromotionOrder: rule.RuleValue.split(',') }),
93
+ 10: rule => ({ DEPromotionAmount: rule.RuleValue }),
94
+ 11: rule => ({ seedWith: rule.RuleValue }),
95
+ 14: rule => ({ CutOffNumber: rule.RuleValue }),
96
+ 15: rule => ({ DEType: rule.RuleValue }),
97
+ 16: rule => ({ PoolScoreDirection: ruleValueIsTrue(rule, 'PoolScoreDirection') }),
98
+ 17: rule => ({ DEPromoteBy: rule.RuleValue }),
99
+ 19: rule => ({ CountHandshots: ruleValueIsTrue(rule, 'CountHandshots') }),
100
+ 20: rule => ({ CountDoubles: ruleValueIsTrue(rule, 'CountDoubles') }),
101
+ 21: rule => ({ PenaltiesReset: ruleValueIsTrue(rule, 'PenaltiesReset') }),
102
+ 22: rule => ({ IncludeInRatings: ruleValueIsTrue(rule, 'IncludeInRatings') })
103
+ };
104
+
105
+ const createRulePatch = rule => (rulePatchCreators[rule.RuleId] || (() => ({})))(rule);
106
+
107
+ const applyRulesToEventConfig = (eventConfig, rules) =>
108
+ rules.reduce((currentConfig, rule) => ({ ...currentConfig, ...createRulePatch(rule) }), { ...eventConfig });
109
+
110
+ const cloneEvent = event => ({
111
+ ...(event || {}),
112
+ rules: Array.isArray(event?.rules) ? event.rules.map(rule => ({ ...rule })) : []
113
+ });
114
+
115
+ const createEventConfig = event => ({
116
+ EventName: event.EventName,
117
+ EventPrice: event.EventPrice ?? '',
118
+ Date: event.startDate ?? event.Date,
119
+ StartTime: event.startTime ?? event.StartTime,
120
+ NumberOfRings: event.NumberOfRings,
121
+ WeaponId: event.WeaponId,
122
+ seedWith: 'M2 - MeyerSquared',
123
+ CutOffNumber: undefined,
124
+ WaitlistEnabled: event.WaitlistEnabled ?? true,
125
+ boutCounter: false,
126
+ boutCount: 5,
127
+ poolTimer: false,
128
+ poolMax: false,
129
+ DEMax: false,
130
+ thirdPlaceBout: false,
131
+ poolTime: 180,
132
+ poolMaxPoints: 7,
133
+ PoolScoreDirection: true,
134
+ DETimer: false,
135
+ DETime: 180,
136
+ DEBoutCounter: false,
137
+ DEBoutCount: 7,
138
+ DEMaxPoints: 12,
139
+ DEPromotionAmount: '100%',
140
+ DEPromotionOrder: ['Win Percent', 'Win Count', 'Indicator', 'Points Scored'],
141
+ DEPromoteBy: 'Percent',
142
+ DEType: 'single',
143
+ CountHandshots: false,
144
+ CountDoubles: false,
145
+ PenaltiesReset: true,
146
+ IncludeInRatings: event.IncludeInRatings ?? true
147
+ });
148
+
149
+ export default {
150
+ name: 'EditEventWizard',
151
+ components: {
152
+ EventCustomization,
153
+ DirectEliminationPromotion,
154
+ StepIndicator,
155
+ BasicEventInfo,
156
+ PoolConfiguration,
157
+ DirectElimination,
158
+ BaseButton
159
+ },
160
+ emits: ['update:submit', 'update:cancel', 'save:step', 'navigate:step', 'update:dirty'],
161
+ props: {
162
+ editMode: { type: Boolean, default: false },
163
+ step: { type: Number, default: 1 },
164
+ saving: { type: Boolean, default: false },
165
+ saveError: { type: String, default: '' },
166
+ saveNotice: { type: String, default: '' },
167
+
168
+ event: {
169
+ type: Object
170
+ },
171
+ weapons: {
172
+ type: Array,
173
+ required: true
174
+ },
175
+ saveButtonLabel: {
176
+ type: String,
177
+ default: 'Save'
178
+ },
179
+ DEFormats: {
180
+ type: Array,
181
+ default: () => [
182
+ { link: 1, text: 'Single Elimination' },
183
+ { link: 2, text: 'Double Elimination' }
184
+ ]
185
+ },
186
+ userLevel: {
187
+ type: String,
188
+ default: 'Club'
189
+ }
190
+ },
191
+ data() {
192
+ return {
193
+ currentStep: this.step,
194
+ localEvent: cloneEvent(this.event),
195
+ hasClickedNext: false,
196
+ validation: {
197
+ EventName: { invalid: false, message: '' },
198
+ EventPrice: { invalid: false, message: '' }
199
+ },
200
+ eventConfig: createEventConfig(this.event),
201
+ savedDraft: '',
202
+ draftVersion: 0,
203
+ seedWithOptions: [
204
+ { text: 'M2 - MeyerSquared', link: 'M2' },
205
+ { text: 'HR - Hema Rating', link: 'HR' },
206
+ { text: 'Manual', link: 'MN' }
207
+ ],
208
+ steps: [
209
+ { name: 'Basic Info', icon: 'fa-circle-info' },
210
+ { name: 'Customization', icon: 'fa-star' },
211
+ { name: 'Pool Setup', icon: 'fa-table-columns' },
212
+ { name: 'DE Promotion', icon: 'fa-list' },
213
+ { name: 'Direct Elimination', icon: 'fa-diagram-sankey' }
214
+ ]
215
+ };
216
+ },
217
+ computed: {
218
+ nextButtonLabel() {
219
+ if (!this.editMode) return this.currentStep === 5 ? 'Submit' : 'Next';
220
+ return this.currentStep === 5 ? 'Save' : 'Save and Continue';
221
+ }
222
+ },
223
+ mounted() {
224
+ this.resetDraft();
225
+ },
226
+ watch: {
227
+ event() {
228
+ this.resetDraft();
229
+ },
230
+ step(value) {
231
+ if (Number.isInteger(value) && value >= 1 && value <= this.steps.length) this.currentStep = value;
232
+ }
233
+ },
234
+ methods: {
235
+ resetDraft() {
236
+ this.localEvent = cloneEvent(this.event);
237
+ this.eventConfig = applyRulesToEventConfig(createEventConfig(this.event), this.localEvent.rules);
238
+ if (typeof this.event.IncludeInRatings === 'boolean')
239
+ this.eventConfig.IncludeInRatings = this.event.IncludeInRatings;
240
+ this.savedDraft = JSON.stringify(this.eventConfig);
241
+ this.draftVersion += 1;
242
+ this.publishDirtyState();
243
+ },
244
+ publishDirtyState() {
245
+ if (this.editMode) this.$emit('update:dirty', JSON.stringify(this.eventConfig) !== this.savedDraft);
246
+ },
247
+ handleValueChange(change) {
248
+ if (this.saving) return;
249
+ this.eventConfig = { ...this.eventConfig, ...change };
250
+ this.localEvent = { ...this.localEvent, ...change };
251
+ this.publishDirtyState();
252
+ if (this.hasClickedNext) this.validateAll();
253
+ },
254
+ handleDEPromotionType(value) {
255
+ this.handleValueChange({ DEPromoteBy: value });
256
+ },
257
+ validateAll() {
258
+ let isValid = true;
259
+ this.validation.EventPrice = validatePrice(this.eventConfig.EventPrice);
260
+ const forbiddenChars = /[\/\\'"=:*<>|?&!@#$%^(){}[\]]/g;
261
+
262
+ if (!this.eventConfig.EventName || this.eventConfig.EventName.trim().length < 4) {
263
+ this.validation.EventName = {
264
+ invalid: true,
265
+ message: 'Event Name must be at least 3 characters long.'
266
+ };
267
+ isValid = false;
268
+ return isValid;
269
+ }
270
+
271
+ if (forbiddenChars.test(this.eventConfig.EventName)) {
272
+ this.validation.EventName = {
273
+ invalid: true,
274
+ message: 'Event Name contains invalid characters.'
275
+ };
276
+ isValid = false;
277
+ return isValid;
278
+ }
279
+
280
+ this.validation.EventName = { invalid: false, message: '' };
281
+ return !this.validation.EventPrice.invalid;
282
+ },
283
+ handleUpdate(eventConfig = {}) {
284
+ if (this.saving) return;
285
+ this.hasClickedNext = true;
286
+ if (!this.editMode) this.eventConfig = { ...this.eventConfig, ...eventConfig };
287
+ if (!this.validateAll()) return;
288
+ if (this.editMode) {
289
+ this.requestStepSave();
290
+ return;
291
+ }
292
+ this.currentStep += 1;
293
+ },
294
+ handleBack() {
295
+ if (this.saving) return;
296
+ if (this.editMode) {
297
+ this.$emit('navigate:step', this.currentStep - 1);
298
+ return;
299
+ }
300
+ this.currentStep -= 1;
301
+ },
302
+ convertEventConfigToRules(eventConfig) {
303
+ const mapping = {
304
+ poolTime: { RuleId: 1, RuleName: 'PoolTime', Context: 'pool', dependsOn: 'poolTimer' },
305
+ poolMaxPoints: { RuleId: 2, RuleName: 'PoolMaxPoints', Context: 'pool', dependsOn: 'poolMax' },
306
+ DETime: { RuleId: 3, RuleName: 'DETime', Context: 'de', dependsOn: 'DETimer' },
307
+ DEMaxPoints: { RuleId: 4, RuleName: 'DEMaxPoints', Context: 'de', dependsOn: 'DEMax' },
308
+ thirdPlaceBout: { RuleId: 5, RuleName: 'DE3rdFenceOff', Context: 'de' },
309
+ boutCount: { RuleId: 6, RuleName: 'PoolNumOfPasses', Context: 'pool', dependsOn: 'boutCounter' },
310
+ DEBoutCount: { RuleId: 7, RuleName: 'DENumOfPasses', Context: 'de', dependsOn: 'DEBoutCounter' },
311
+ DEPromotionAmount: { RuleId: 10, RuleName: 'DENumOfPromotion', Context: 'de' },
312
+ DEPromotionOrder: { RuleId: 9, RuleName: 'DEPromotionOrder', Context: 'de' },
313
+ seedWith: { RuleId: 11, RuleName: 'PoolSeeding', Context: 'pool' },
314
+ CutOffNumber: { RuleId: 14, RuleName: 'CutOffNumber', Context: 'event' },
315
+ DEType: { RuleId: 15, RuleName: 'DEType', Context: 'de' },
316
+ PoolScoreDirection: { RuleId: 16, RuleName: 'PoolScoreDirection', Context: 'de' },
317
+ DEPromoteBy: { RuleId: 17, RuleName: 'DEPromoteBy', Context: 'de' },
318
+ CountHandshots: { RuleId: 19, RuleName: 'CountHandshots', Context: 'event' },
319
+ CountDoubles: { RuleId: 20, RuleName: 'CountDoubles', Context: 'event' },
320
+ PenaltiesReset: { RuleId: 21, RuleName: 'PenaltiesReset', Context: 'event' },
321
+ IncludeInRatings: { RuleId: 22, RuleName: 'IncludeInRatings', Context: 'event', onlyWhenExisting: true }
322
+ };
323
+
324
+ return Object.entries(eventConfig)
325
+ .filter(([key, value]) => mapping[key] && value !== undefined)
326
+ .filter(
327
+ ([key]) =>
328
+ !mapping[key].onlyWhenExisting ||
329
+ this.localEvent.rules.some(rule => Number(rule.RuleId) === mapping[key].RuleId)
330
+ )
331
+ .filter(([key]) => {
332
+ const dependency = mapping[key].dependsOn;
333
+ return !dependency || eventConfig[dependency];
334
+ })
335
+ .map(([key, value]) => ({
336
+ RuleId: mapping[key].RuleId,
337
+ RuleName: mapping[key].RuleName,
338
+ RuleValue: this.serializeRuleValue(value),
339
+ Context: mapping[key].Context
340
+ }));
341
+ },
342
+ serializeRuleValue(value) {
343
+ if (Array.isArray(value)) return value.join(',');
344
+ const isDropdownOption = value !== null && typeof value === 'object';
345
+ return String(isDropdownOption ? (value.link ?? value.text ?? '') : value);
346
+ },
347
+ buildEventPayload() {
348
+ return {
349
+ ...cloneEvent(this.localEvent),
350
+ ...this.eventConfig,
351
+ EventPrice: Number(this.eventConfig.EventPrice),
352
+ rules: this.convertEventConfigToRules(this.eventConfig)
353
+ };
354
+ },
355
+ requestStepSave() {
356
+ this.$emit('save:step', { event: this.buildEventPayload(), step: this.currentStep });
357
+ },
358
+ handleSubmit() {
359
+ if (this.saving || !this.validateAll()) return;
360
+ if (this.editMode) {
361
+ this.requestStepSave();
362
+ return;
363
+ }
364
+ this.$emit('update:submit', this.buildEventPayload());
365
+ },
366
+ handleCancel() {
367
+ if (this.saving) return;
368
+ this.$emit('update:cancel');
369
+ }
370
+ }
371
+ };
372
+ </script>