@dcrackel/hematournamentui 1.0.709 → 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 (24) hide show
  1. package/README.md +16 -0
  2. package/dist/HemaTournamentUI-lib.es.js +4332 -4090
  3. package/dist/HemaTournamentUI-lib.umd.js +27 -27
  4. package/dist/hematournamentui-tailwind.css +3 -0
  5. package/package.json +1 -1
  6. package/src/stories/Organisms/Wizards/EditEventWizard/BasicEventInfo/BasicEventInfo.stories.js +48 -41
  7. package/src/stories/Organisms/Wizards/EditEventWizard/BasicEventInfo/BasicEventInfo.vue +227 -206
  8. package/src/stories/Organisms/Wizards/EditEventWizard/DirectElimination/DirectElimination.vue +226 -225
  9. package/src/stories/Organisms/Wizards/EditEventWizard/DirectEliminationPromotion/DirectEliminationPromotion.vue +204 -204
  10. package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.stories.js +133 -91
  11. package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.test.js +191 -0
  12. package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.vue +366 -309
  13. package/src/stories/Organisms/Wizards/EditEventWizard/EventCustomization/EventCustomization.vue +172 -171
  14. package/src/stories/Organisms/Wizards/EditEventWizard/PoolConfiguration/PoolConfiguration.vue +216 -215
  15. package/src/stories/Organisms/Wizards/EditEventWizard/PriceFields.test.js +79 -0
  16. package/src/stories/Templates/EventManagement/EditEventInfo/EditEventInfo.stories.js +93 -52
  17. package/src/stories/Templates/EventManagement/EditEventInfo/EditEventInfo.vue +91 -75
  18. package/src/stories/Templates/TournamentManagement/TournamentManagement.stories.js +84 -83
  19. package/src/stories/Templates/TournamentManagement/TournamentManagement.test.js +89 -0
  20. package/src/stories/Templates/TournamentManagement/TournamentManagement.vue +363 -360
  21. package/src/stories/Templates/TournamentManagement/TournamentPageOne/TournamentPageOne.stories.js +116 -113
  22. package/src/stories/Templates/TournamentManagement/TournamentPageOne/TournamentPageOne.vue +292 -280
  23. package/src/stories/Util/Validations/PriceValidation.js +6 -0
  24. package/src/stories/Util/Validations/TournamentFieldsValidations.js +4 -0
@@ -1,309 +1,366 @@
1
- <template>
2
- <div class="p-4 max-h-screen overflow-y-auto">
3
- <BasicEventInfo
4
- v-if="currentStep === 1"
5
- :eventConfig="eventConfig"
6
- :weapons="weapons"
7
- :validation="validation"
8
- :steps="steps"
9
- :currentStep="currentStep - 1"
10
- :userLevel="userLevel"
11
- @update="handleValueChange"
12
- @update:back="handleCancel"
13
- @update:next="handleUpdate"
14
- />
15
- <EventCustomization
16
- v-if="currentStep === 2"
17
- :eventConfig="eventConfig"
18
- :steps="steps"
19
- :currentStep="currentStep - 1"
20
- @update="handleValueChange"
21
- @update:back="handleBack"
22
- @update:next="handleUpdate"
23
- />
24
- <PoolConfiguration
25
- v-if="currentStep === 3"
26
- :eventConfig="eventConfig"
27
- :seedWithOptions="seedWithOptions"
28
- :validation="validation"
29
- :steps="steps"
30
- :currentStep="currentStep - 1"
31
- @update="handleValueChange"
32
- @update:back="handleBack"
33
- @update:next="handleUpdate"
34
- />
35
- <DirectEliminationPromotion
36
- v-if="currentStep === 4"
37
- :eventConfig="eventConfig"
38
- :validation="validation"
39
- :steps="steps"
40
- :currentStep="currentStep - 1"
41
- @update="handleValueChange"
42
- @update:DEPromotionType="handleDEPromotionType"
43
- @update:back="handleBack"
44
- @update:next="handleUpdate"
45
- />
46
- <DirectElimination
47
- v-if="currentStep === 5"
48
- :eventConfig="eventConfig"
49
- :validation="validation"
50
- :steps="steps"
51
- :currentStep="currentStep - 1"
52
- :DEFormats="DEFormats"
53
- @update="handleValueChange"
54
- @update:back="handleBack"
55
- @update:submit="handleSubmit"
56
- />
57
- </div>
58
- </template>
59
-
60
- <script>
61
- import BasicEventInfo from './BasicEventInfo/BasicEventInfo.vue';
62
- import PoolConfiguration from './PoolConfiguration/PoolConfiguration.vue';
63
- import DirectElimination from './DirectElimination/DirectElimination.vue';
64
- import BaseButton from '../../../Molecules/Buttons/BaseButton/BaseButton.vue';
65
- import StepIndicator from '../../../Molecules/StepIndicator/BaseStepIndicator/StepIndicator.vue';
66
- import DirectEliminationPromotion from './DirectEliminationPromotion/DirectEliminationPromotion.vue';
67
- import EventCustomization from '../../..//Organisms/Wizards/EditEventWizard/EventCustomization/EventCustomization.vue';
68
-
69
- const ruleValueIsTrue = (rule, fallbackField) =>
70
- rule.RuleValue === 'true' || rule.RuleValue === true || rule[fallbackField] === true;
71
-
72
- const rulePatchCreators = {
73
- 1: rule => ({ poolTime: rule.RuleValue, poolTimer: true }),
74
- 2: rule => ({ poolMaxPoints: rule.RuleValue, poolMax: true }),
75
- 3: rule => ({ DETime: rule.RuleValue, DETimer: true }),
76
- 4: rule => ({ DEMaxPoints: rule.RuleValue, DEMax: true }),
77
- 5: rule => ({ thirdPlaceBout: ruleValueIsTrue(rule, 'thirdPlaceBout') }),
78
- 6: rule => ({ boutCount: rule.RuleValue, boutCounter: true }),
79
- 7: rule => ({ DEBoutCount: rule.RuleValue, DEBoutCounter: true }),
80
- 9: rule => ({ DEPromotionOrder: rule.RuleValue.split(',') }),
81
- 10: rule => ({ DEPromotionAmount: rule.RuleValue }),
82
- 11: rule => ({ seedWith: rule.RuleValue }),
83
- 14: rule => ({ CutOffNumber: rule.RuleValue }),
84
- 15: rule => ({ DEType: rule.RuleValue }),
85
- 16: rule => ({ PoolScoreDirection: ruleValueIsTrue(rule, 'PoolScoreDirection') }),
86
- 17: rule => ({ DEPromoteBy: rule.RuleValue }),
87
- 19: rule => ({ CountHandshots: ruleValueIsTrue(rule, 'CountHandshots') }),
88
- 20: rule => ({ CountDoubles: ruleValueIsTrue(rule, 'CountDoubles') }),
89
- 21: rule => ({ PenaltiesReset: ruleValueIsTrue(rule, 'PenaltiesReset') }),
90
- 22: rule => ({ IncludeInRatings: ruleValueIsTrue(rule, 'IncludeInRatings') })
91
- };
92
-
93
- const createRulePatch = rule => (rulePatchCreators[rule.RuleId] || (() => ({})))(rule);
94
-
95
- const applyRulesToEventConfig = (eventConfig, rules) =>
96
- rules.reduce((currentConfig, rule) => ({ ...currentConfig, ...createRulePatch(rule) }), { ...eventConfig });
97
-
98
- const cloneEvent = event => ({
99
- ...(event || {}),
100
- rules: Array.isArray(event?.rules) ? event.rules.map(rule => ({ ...rule })) : []
101
- });
102
-
103
- export default {
104
- name: 'EditEventWizard',
105
- components: {
106
- EventCustomization,
107
- DirectEliminationPromotion,
108
- StepIndicator,
109
- BasicEventInfo,
110
- PoolConfiguration,
111
- DirectElimination,
112
- BaseButton
113
- },
114
- emits: ['update:submit', 'update:cancel'],
115
- props: {
116
- event: {
117
- type: Object
118
- },
119
- weapons: {
120
- type: Array,
121
- required: true
122
- },
123
- saveButtonLabel: {
124
- type: String,
125
- default: 'Save'
126
- },
127
- DEFormats: {
128
- type: Array,
129
- default: () => [
130
- { link: 1, text: 'Single Elimination' },
131
- { link: 2, text: 'Double Elimination' }
132
- ]
133
- },
134
- userLevel: {
135
- type: String,
136
- default: 'Club'
137
- }
138
- },
139
- data() {
140
- return {
141
- currentStep: 1,
142
- localEvent: cloneEvent(this.event),
143
- hasClickedNext: false,
144
- validation: {
145
- EventName: { invalid: false, message: '' }
146
- },
147
- eventConfig: {
148
- EventName: this.event.EventName,
149
- Date: this.event.startDate,
150
- StartTime: this.event.startTime,
151
- NumberOfRings: this.event.NumberOfRings,
152
- WeaponId: this.event.WeaponId,
153
- seedWith: { text: 'M2 - MeyerSquared', link: 'M2' },
154
- CutOffNumber: undefined,
155
- boutCounter: false,
156
- boutCount: 5,
157
- poolTimer: false,
158
- poolMax: false,
159
- DEMax: false,
160
- thirdPlaceBout: false,
161
- poolTime: 180,
162
- poolMaxPoints: 7,
163
- PoolScoreDirection: true,
164
- DETimer: false,
165
- DETime: 180,
166
- DEBoutCounter: false,
167
- DEBoutCount: 7,
168
- DEMaxPoints: 12,
169
- DEPromotionAmount: '100%',
170
- DEPromotionOrder: ['Win Percent', 'Win Count', 'Indicator', 'Points Scored'],
171
- DEPromoteBy: ['Top Percent', 'Top Count', 'Top Count of Each Pool'],
172
- DEType: 'single',
173
- CountHandshots: false,
174
- CountDoubles: false,
175
- PenaltiesReset: true,
176
- IncludeInRatings: true
177
- },
178
- seedWithOptions: [
179
- { text: 'M2 - MeyerSquared', link: 'M2' },
180
- { text: 'HR - Hema Rating', link: 'HR' },
181
- { text: 'Manual', link: 'MN' }
182
- ],
183
- steps: [
184
- { name: 'Basic Info', icon: 'fa-circle-info' },
185
- { name: 'Customization', icon: 'fa-star' },
186
- { name: 'Pool Setup', icon: 'fa-table-columns' },
187
- { name: 'DE Promotion', icon: 'fa-list' },
188
- { name: 'Direct Elimination', icon: 'fa-diagram-sankey' }
189
- ]
190
- };
191
- },
192
- mounted() {
193
- this.setInitialValues();
194
- if (this.eventConfig.EventId > 0) this.validateAll();
195
- },
196
- watch: {
197
- event(newEvent) {
198
- this.localEvent = cloneEvent(newEvent);
199
- }
200
- },
201
- methods: {
202
- setInitialValues() {
203
- if (!this.event.rules) return;
204
- this.eventConfig = applyRulesToEventConfig(this.eventConfig, this.event.rules);
205
- },
206
- handleValueChange(change) {
207
- const [key, value] = Object.entries(change)[0];
208
-
209
- if (key in this.eventConfig) {
210
- this.eventConfig[key] = value;
211
- }
212
-
213
- this.localEvent[key] = value;
214
-
215
- if (this.hasClickedNext) {
216
- this.validateAll();
217
- }
218
- },
219
- handleDEPromotionType(change) {
220
- this.eventConfig.DEPromoteBy = change;
221
- this.localEvent.DEPromoteBy = this.eventConfig.DEPromoteBy;
222
- },
223
- validateAll() {
224
- let isValid = true;
225
- const forbiddenChars = /[\/\\'"=:*<>|?&!@#$%^(){}[\]]/g;
226
-
227
- if (!this.eventConfig.EventName || this.eventConfig.EventName.trim().length < 4) {
228
- this.validation.EventName = {
229
- invalid: true,
230
- message: 'Event Name must be at least 3 characters long.'
231
- };
232
- isValid = false;
233
- return isValid;
234
- }
235
-
236
- if (forbiddenChars.test(this.eventConfig.EventName)) {
237
- this.validation.EventName = {
238
- invalid: true,
239
- message: 'Event Name contains invalid characters.'
240
- };
241
- isValid = false;
242
- return isValid;
243
- }
244
-
245
- this.validation.EventName = { invalid: false, message: '' };
246
- return isValid;
247
- },
248
- handleUpdate(eventConfig) {
249
- this.hasClickedNext = true;
250
-
251
- if (this.currentStep === 1) {
252
- if (!this.validateAll()) {
253
- return;
254
- }
255
- }
256
-
257
- this.eventConfig = { ...this.eventConfig, ...eventConfig };
258
- this.currentStep += 1;
259
- },
260
- handleBack() {
261
- this.currentStep -= 1;
262
- },
263
- convertEventConfigToRules(eventConfig) {
264
- const mapping = {
265
- poolTime: { RuleId: 1, RuleName: 'PoolTime', Context: 'pool', dependsOn: 'poolTimer' },
266
- poolMaxPoints: { RuleId: 2, RuleName: 'PoolMaxPoints', Context: 'pool' },
267
- DETime: { RuleId: 3, RuleName: 'DETime', Context: 'de', dependsOn: 'DETimer' },
268
- DEMaxPoints: { RuleId: 4, RuleName: 'DEMaxPoints', Context: 'de' },
269
- thirdPlaceBout: { RuleId: 5, RuleName: 'DE3rdFenceOff', Context: 'de' },
270
- boutCount: { RuleId: 6, RuleName: 'PoolNumOfPasses', Context: 'pool', dependsOn: 'boutCounter' },
271
- DEBoutCount: { RuleId: 7, RuleName: 'DENumOfPasses', Context: 'de', dependsOn: 'DEBoutCounter' },
272
- DEPromotionAmount: { RuleId: 10, RuleName: 'DENumOfPromotion', Context: 'de' },
273
- DEPromotionOrder: { RuleId: 9, RuleName: 'DEPromotionOrder', Context: 'de' },
274
- seedWith: { RuleId: 11, RuleName: 'PoolSeeding', Context: 'pool' },
275
- CutOffNumber: { RuleId: 14, RuleName: 'CutOffNumber', Context: 'event' },
276
- DEType: { RuleId: 15, RuleName: 'DEType', Context: 'de' },
277
- PoolScoreDirection: { RuleId: 16, RuleName: 'PoolScoreDirection', Context: 'de' },
278
- DEPromoteBy: { RuleId: 17, RuleName: 'DEPromoteBy', Context: 'de' },
279
- CountHandshots: { RuleId: 19, RuleName: 'CountHandshots', Context: 'event' },
280
- CountDoubles: { RuleId: 20, RuleName: 'CountDoubles', Context: 'event' },
281
- PenaltiesReset: { RuleId: 21, RuleName: 'PenaltiesReset', Context: 'event' }
282
- };
283
-
284
- return Object.entries(eventConfig)
285
- .filter(([key]) => mapping[key])
286
- .filter(([key]) => {
287
- const dependency = mapping[key].dependsOn;
288
- return !dependency || eventConfig[dependency];
289
- })
290
- .map(([key, value]) => ({
291
- RuleId: mapping[key].RuleId,
292
- RuleName: mapping[key].RuleName,
293
- RuleValue: Array.isArray(value) ? value.join(',') : String(value),
294
- Context: mapping[key].Context
295
- }));
296
- },
297
- handleSubmit() {
298
- this.localEvent = {
299
- ...this.localEvent,
300
- rules: this.convertEventConfigToRules(this.eventConfig)
301
- };
302
- this.$emit('update:submit', { ...this.localEvent });
303
- },
304
- handleCancel() {
305
- this.$emit('update:cancel');
306
- }
307
- }
308
- };
309
- </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
+ 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>