@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.
- package/.prettierignore +7 -7
- package/.storybook/main.js +3 -1
- package/README.md +42 -0
- package/dist/HemaTournamentUI-lib.es.js +6856 -6243
- package/dist/HemaTournamentUI-lib.umd.js +27 -27
- package/dist/hematournamentui-tailwind.css +79 -9
- package/package.json +96 -96
- package/prettier.config.js +16 -16
- package/src/index.js +43 -42
- package/src/main.js +1 -1
- package/src/mocks/getDEBouts-TableOf128.js +37 -37
- package/src/mocks/getDEBouts-TableOf64.js +35 -35
- package/src/stories/Atoms/Input/BaseInput.test.js +97 -97
- package/src/stories/Atoms/Input/BaseInput.vue +149 -149
- package/src/stories/Molecules/Indicators/MissingHemaRatingsId/MissingHemaRatingsId.stories.js +23 -23
- package/src/stories/Molecules/Indicators/MissingHemaRatingsId/MissingHemaRatingsId.vue +35 -35
- package/src/stories/Molecules/Indicators/RegistrationStatus/RegistrationStatus.vue +30 -30
- package/src/stories/Molecules/Menus/HoverActions/HoverActions.test.js +138 -0
- package/src/stories/Molecules/Menus/HoverActions/HoverActions.vue +212 -0
- package/src/stories/Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.stories.js +49 -0
- package/src/stories/Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.test.js +129 -0
- package/src/stories/Molecules/Modals/BracketDirectorsModal/BracketDirectorsModal.vue +165 -0
- package/src/stories/Molecules/Modals/DirectorModal/DirectorModal.stories.js +6 -0
- package/src/stories/Molecules/Modals/DirectorModal/DirectorModal.test.js +18 -18
- package/src/stories/Molecules/Modals/DirectorModal/DirectorModal.vue +1 -1
- package/src/stories/Organisms/Cards/BoutCard/BoutCard.vue +5 -1
- package/src/stories/Organisms/Cards/BoutCard/Variants/BoutCardActive/BoutCardActive.vue +19 -10
- package/src/stories/Organisms/Cards/BoutCard/Variants/BoutCardInactive/BoutCardInactive.vue +11 -7
- package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.stories.js +42 -33
- package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.test.js +76 -45
- package/src/stories/Organisms/Cards/BracketDirectorAssignmentCard/BracketDirectorAssignmentCard.vue +122 -103
- package/src/stories/Organisms/Cards/Director/DirectorCard.test.js +82 -0
- package/src/stories/Organisms/Cards/Director/DirectorCard.vue +32 -51
- package/src/stories/Organisms/Cards/EditBout/EditBout.test.js +51 -51
- package/src/stories/Organisms/Cards/EventCard/EventCard.test.js +106 -106
- package/src/stories/Organisms/Cards/FencerCard/FencerCard.vue +298 -298
- package/src/stories/Organisms/Cards/TableauBoutCard/TableauBoutCard.stories.js +77 -60
- package/src/stories/Organisms/Cards/TableauBoutCard/TableauBoutCard.test.js +144 -143
- package/src/stories/Organisms/Cards/TableauBoutCard/TableauBoutCard.vue +289 -256
- package/src/stories/Organisms/Cards/TableauFencerCard/TableauFencerCard.stories.js +69 -69
- package/src/stories/Organisms/Cards/TableauFencerCard/TableauFencerCard.vue +80 -80
- package/src/stories/Organisms/Cards/WaitlistFencerCard/WaitlistFencerCard.vue +153 -153
- package/src/stories/Organisms/ComplexInputs/DropDown/DropDownMenu.vue +4 -0
- package/src/stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.stories.js +31 -31
- package/src/stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.test.js +109 -109
- package/src/stories/Organisms/ComplexInputs/ImageCropper/ImageCropper.vue +192 -192
- package/src/stories/Organisms/Containers/Tableau/Tableau.stories.js +1 -0
- package/src/stories/Organisms/Containers/Tableau/Tableau.test.js +247 -204
- package/src/stories/Organisms/Containers/Tableau/Tableau.vue +89 -15
- package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.test.js +51 -29
- package/src/stories/Organisms/Containers/Tableau/components/BracketDirectorAssignmentPanel.vue +63 -46
- package/src/stories/Organisms/Containers/Tableau/components/TableauSvgOverlay.test.js +21 -21
- package/src/stories/Organisms/Containers/Tableau/components/TableauSvgOverlay.vue +39 -39
- package/src/stories/Organisms/Containers/Tableau/layout/TableauLayoutEngine.js +145 -145
- package/src/stories/Organisms/Containers/Tableau/layout/TableauLayoutEngine.test.js +64 -64
- package/src/stories/Organisms/Containers/Tableau/plannedAssignments.js +36 -57
- package/src/stories/Organisms/Containers/Tableau/plannedAssignments.test.js +101 -65
- package/src/stories/Organisms/Containers/TableauColumn/TableauColumn.vue +14 -1
- package/src/stories/Organisms/Wizards/EditEventWizard/BasicEventInfo/BasicEventInfo.stories.js +52 -52
- package/src/stories/Organisms/Wizards/EditEventWizard/BasicEventInfo/BasicEventInfo.vue +10 -3
- package/src/stories/Organisms/Wizards/EditEventWizard/DirectElimination/DirectElimination.vue +226 -226
- package/src/stories/Organisms/Wizards/EditEventWizard/DirectEliminationPromotion/DirectEliminationPromotion.vue +204 -204
- package/src/stories/Organisms/Wizards/EditEventWizard/EditEventWizard.stories.js +137 -137
- package/src/stories/Organisms/Wizards/EditEventWizard/EventCustomization/EventCustomization.vue +172 -172
- package/src/stories/Organisms/Wizards/EditEventWizard/PoolConfiguration/PoolConfiguration.vue +216 -216
- package/src/stories/Organisms/Wizards/EditEventWizard/PriceFields.test.js +79 -79
- package/src/stories/Organisms/Wizards/EditEventWizard/RuleSerialization.test.js +29 -29
- package/src/stories/Organisms/Wizards/EditEventWizard/WeaponSelection.test.js +138 -0
- package/src/stories/Templates/EventManagement/Bracket/Bracket.stories.js +149 -9
- package/src/stories/Templates/EventManagement/Bracket/Bracket.test.js +54 -2
- package/src/stories/Templates/EventManagement/Bracket/Bracket.vue +77 -8
- package/src/stories/Templates/EventManagement/Bracket/BracketDirectorAssignment.test.js +242 -0
- package/src/stories/Templates/EventManagement/Bracket/BracketMobile.test.js +131 -0
- package/src/stories/Templates/EventManagement/Bracket/BracketPlannedSuggestions.js +50 -39
- package/src/stories/Templates/EventManagement/Bracket/BracketStories.test.js +201 -0
- package/src/stories/Templates/EventManagement/Bracket/BracketStoryAssignments.js +93 -0
- package/src/stories/Templates/EventManagement/Bracket/BracketStoryAssignments.test.js +152 -0
- package/src/stories/Templates/EventManagement/Bracket/BracketStoryHarness.js +39 -39
- package/src/stories/Templates/EventManagement/Bracket/BracketStoryHarness.test.js +79 -79
- package/src/stories/Templates/EventManagement/EditEventInfo/EditEventInfo.stories.js +93 -93
- package/src/stories/Templates/EventManagement/EditEventInfo/EditEventInfo.vue +91 -91
- package/src/stories/Templates/EventManagement/PoolLive/PoolLive.test.js +121 -121
- package/src/stories/Templates/Menus/EditEventsTopMenu/EditEventsTopMenu.stories.js +9 -2
- package/src/stories/Templates/Menus/EditEventsTopMenu/EditEventsTopMenu.vue +5 -4
- package/src/stories/Templates/PersonManagement/Admin/Administration/Administration.stories.js +81 -81
- package/src/stories/Templates/PersonManagement/Admin/Administration/Administration.test.js +39 -39
- package/src/stories/Templates/PersonManagement/Admin/Administration/Administration.vue +101 -101
- package/src/stories/Templates/PersonManagement/Admin/FencerAdmin/FencerAdmin.stories.js +71 -71
- package/src/stories/Templates/PersonManagement/Admin/FencerAdmin/FencerAdmin.vue +86 -86
- package/src/stories/Templates/PersonManagement/Admin/MergeAccounts/MergeAccounts.stories.js +86 -86
- package/src/stories/Templates/PersonManagement/Admin/MergeAccounts/MergeAccounts.vue +247 -247
- package/src/stories/Templates/PersonManagement/Basic/BasicEdit.vue +170 -170
- package/src/stories/Templates/PersonManagement/PersonManagement.stories.js +156 -156
- package/src/stories/Templates/PersonManagement/PersonManagement.test.js +329 -329
- package/src/stories/Templates/PersonManagement/PersonManagement.vue +329 -329
- package/src/stories/Templates/PersonManagement/PersonManagementDraft.js +42 -42
- package/src/stories/Templates/TournamentManagement/TournamentManagement.stories.js +84 -84
- package/src/stories/Templates/TournamentManagement/TournamentManagement.test.js +114 -114
- package/src/stories/Templates/TournamentManagement/TournamentManagement.vue +363 -363
- package/src/stories/Templates/TournamentManagement/TournamentPageOne/TournamentPageOne.stories.js +116 -116
- package/src/stories/Templates/TournamentManagement/TournamentPageOne/TournamentPageOne.vue +307 -307
- package/src/stories/Util/Validations/PersonManagementValidations.js +45 -45
- package/src/stories/Util/Validations/PriceValidation.js +6 -6
- package/src/stories/Util/boutDirectorAssignment.js +46 -0
- package/src/stories/Util/bracketDirectors.js +86 -0
- package/src/stories/Util/registrationStatus.js +11 -11
- package/src/stories/Util/tabs.js +19 -47
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Teleport to="body">
|
|
3
|
+
<dialog
|
|
4
|
+
ref="dialog"
|
|
5
|
+
aria-label="DE directors"
|
|
6
|
+
data-testid="bracket-directors-modal"
|
|
7
|
+
class="fixed inset-0 m-auto max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-xl overflow-y-auto rounded-xl border border-dropdownSelect bg-neutral p-0 text-primaryHighlight shadow-xl backdrop:bg-secondary/60"
|
|
8
|
+
@cancel.prevent="close"
|
|
9
|
+
@click="close"
|
|
10
|
+
>
|
|
11
|
+
<form class="p-5" @submit.prevent="submit" @click.stop>
|
|
12
|
+
<div class="mb-4 flex items-start justify-between gap-3">
|
|
13
|
+
<div>
|
|
14
|
+
<h2 class="text-xl font-bold">Directors</h2>
|
|
15
|
+
<p class="mt-1 text-sm text-quaternary">Select DE directors and choose their rings.</p>
|
|
16
|
+
</div>
|
|
17
|
+
<button
|
|
18
|
+
type="button"
|
|
19
|
+
aria-label="Close director assignments"
|
|
20
|
+
class="flex h-8 w-8 flex-none items-center justify-center rounded text-xl text-quaternary hover:bg-dropdownSelect focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-bright"
|
|
21
|
+
@click="close"
|
|
22
|
+
>
|
|
23
|
+
×
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
<p v-if="!draft.length" class="rounded border border-dropdownSelect p-4 text-sm text-quaternary">
|
|
27
|
+
No directors available.
|
|
28
|
+
</p>
|
|
29
|
+
<div class="space-y-3">
|
|
30
|
+
<div
|
|
31
|
+
v-for="row in draft"
|
|
32
|
+
:key="row.Director.PersonId"
|
|
33
|
+
class="flex items-center gap-3 rounded-lg border border-dropdownSelect px-3 py-3"
|
|
34
|
+
>
|
|
35
|
+
<label class="flex min-w-0 flex-1 cursor-pointer items-center gap-3">
|
|
36
|
+
<input
|
|
37
|
+
v-model="row.Selected"
|
|
38
|
+
type="checkbox"
|
|
39
|
+
:data-director-id="row.Director.PersonId"
|
|
40
|
+
:aria-label="'Include ' + row.Director.DisplayName + ' in DE'"
|
|
41
|
+
class="h-5 w-5 flex-none accent-bright"
|
|
42
|
+
/>
|
|
43
|
+
<span
|
|
44
|
+
v-if="row.Color"
|
|
45
|
+
class="h-2.5 w-2.5 flex-none rounded-full"
|
|
46
|
+
:style="{ backgroundColor: row.Color }"
|
|
47
|
+
aria-hidden="true"
|
|
48
|
+
></span>
|
|
49
|
+
<span class="min-w-0 wrap-break-word text-sm font-semibold">{{ row.Director.DisplayName }}</span>
|
|
50
|
+
</label>
|
|
51
|
+
<select
|
|
52
|
+
v-model="row.RingId"
|
|
53
|
+
:disabled="!row.Selected"
|
|
54
|
+
:data-ring-director-id="row.Director.PersonId"
|
|
55
|
+
:aria-label="'Ring for ' + row.Director.DisplayName"
|
|
56
|
+
class="w-28 flex-none rounded border border-dropdownSelect bg-white px-2 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-bright disabled:opacity-50"
|
|
57
|
+
>
|
|
58
|
+
<option value="">Choose ring</option>
|
|
59
|
+
<option v-for="ring in ringOptions" :key="ring.RingId" :value="ring.RingId" :disabled="ring.Disabled">
|
|
60
|
+
{{ ring.RingName }}
|
|
61
|
+
</option>
|
|
62
|
+
</select>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
<p v-if="error" role="alert" class="mt-3 text-sm text-alarm">{{ error }}</p>
|
|
66
|
+
<div class="mt-5 flex justify-end gap-2">
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
class="rounded border border-dropdownSelect px-4 py-2 text-sm hover:bg-dropdownSelect focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-bright"
|
|
70
|
+
@click="close"
|
|
71
|
+
>
|
|
72
|
+
Cancel
|
|
73
|
+
</button>
|
|
74
|
+
<button
|
|
75
|
+
type="submit"
|
|
76
|
+
:disabled="Boolean(error)"
|
|
77
|
+
class="rounded bg-primary px-4 py-2 text-sm font-semibold text-neutral hover:bg-primaryHighlight focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-bright disabled:cursor-not-allowed disabled:opacity-50"
|
|
78
|
+
>
|
|
79
|
+
Save assignments
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
</form>
|
|
83
|
+
</dialog>
|
|
84
|
+
</Teleport>
|
|
85
|
+
</template>
|
|
86
|
+
|
|
87
|
+
<script>
|
|
88
|
+
import {
|
|
89
|
+
createDirectorDraft,
|
|
90
|
+
reconcileDirectorDraft,
|
|
91
|
+
createDirectorStations,
|
|
92
|
+
getDirectorDraftError,
|
|
93
|
+
getRingOptions
|
|
94
|
+
} from '../../../Util/bracketDirectors.js';
|
|
95
|
+
|
|
96
|
+
export default {
|
|
97
|
+
name: 'BracketDirectorsModal',
|
|
98
|
+
emits: ['update:show', 'submit:stations'],
|
|
99
|
+
props: {
|
|
100
|
+
show: { type: Boolean, default: false },
|
|
101
|
+
availableDirectors: { type: Array, default: () => [] },
|
|
102
|
+
rings: { type: Array, default: () => [] },
|
|
103
|
+
stations: { type: Array, default: () => [] }
|
|
104
|
+
},
|
|
105
|
+
data() {
|
|
106
|
+
return { draft: [] };
|
|
107
|
+
},
|
|
108
|
+
computed: {
|
|
109
|
+
ringOptions() {
|
|
110
|
+
return getRingOptions(this.rings);
|
|
111
|
+
},
|
|
112
|
+
error() {
|
|
113
|
+
return getDirectorDraftError(this.draft, this.rings);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
watch: {
|
|
117
|
+
show: {
|
|
118
|
+
immediate: true,
|
|
119
|
+
handler(value) {
|
|
120
|
+
if (value) this.resetDraft();
|
|
121
|
+
this.$nextTick(this.syncDialog);
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
stations: {
|
|
125
|
+
deep: true,
|
|
126
|
+
handler() {
|
|
127
|
+
if (this.show) this.resetDraft();
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
availableDirectors: {
|
|
131
|
+
deep: true,
|
|
132
|
+
handler() {
|
|
133
|
+
if (!this.show) return;
|
|
134
|
+
this.draft = reconcileDirectorDraft(this.availableDirectors, this.stations, this.draft);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
mounted() {
|
|
139
|
+
this.syncDialog();
|
|
140
|
+
},
|
|
141
|
+
methods: {
|
|
142
|
+
resetDraft() {
|
|
143
|
+
this.draft = createDirectorDraft(this.availableDirectors, this.stations);
|
|
144
|
+
},
|
|
145
|
+
syncDialog() {
|
|
146
|
+
const dialog = this.$refs.dialog;
|
|
147
|
+
if (!dialog) return;
|
|
148
|
+
if (this.show && !dialog.open) {
|
|
149
|
+
dialog.showModal();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (!this.show && dialog.open) dialog.close();
|
|
153
|
+
},
|
|
154
|
+
close() {
|
|
155
|
+
this.$emit('update:show', false);
|
|
156
|
+
},
|
|
157
|
+
submit() {
|
|
158
|
+
const stations = createDirectorStations(this.draft, this.rings);
|
|
159
|
+
if (!stations) return;
|
|
160
|
+
this.$emit('submit:stations', stations);
|
|
161
|
+
this.close();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
</script>
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { shallowMount } from '@vue/test-utils';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import DirectorModal from './DirectorModal.vue';
|
|
4
|
-
|
|
5
|
-
describe('DirectorModal backdrop', () => {
|
|
6
|
-
it('keeps the directing screen visible through a 25 percent backdrop', () => {
|
|
7
|
-
const wrapper = shallowMount(DirectorModal, {
|
|
8
|
-
props: {
|
|
9
|
-
show: true,
|
|
10
|
-
bout: { BoutId: 42 },
|
|
11
|
-
eventRules: {},
|
|
12
|
-
hostingClubColors: {}
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
expect(wrapper.find('[data-testid="director-modal-backdrop"]').classes()).toContain('bg-secondary/25');
|
|
17
|
-
});
|
|
18
|
-
});
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import DirectorModal from './DirectorModal.vue';
|
|
4
|
+
|
|
5
|
+
describe('DirectorModal backdrop', () => {
|
|
6
|
+
it('keeps the directing screen visible through a 25 percent backdrop', () => {
|
|
7
|
+
const wrapper = shallowMount(DirectorModal, {
|
|
8
|
+
props: {
|
|
9
|
+
show: true,
|
|
10
|
+
bout: { BoutId: 42 },
|
|
11
|
+
eventRules: {},
|
|
12
|
+
hostingClubColors: {}
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
expect(wrapper.find('[data-testid="director-modal-backdrop"]').classes()).toContain('bg-secondary/25');
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -51,16 +51,24 @@
|
|
|
51
51
|
|
|
52
52
|
<!-- Mobile active -->
|
|
53
53
|
<div
|
|
54
|
-
class="flex md:hidden justify-between items-center
|
|
55
|
-
:class="mobileCardActionClass"
|
|
54
|
+
class="flex md:hidden justify-between items-center px-3 py-2 border-alarm rounded-md bg-poolBox transition"
|
|
55
|
+
:class="[mobileCardActionClass, mobileCardLayoutClass]"
|
|
56
56
|
@click="onCardClick"
|
|
57
57
|
>
|
|
58
|
-
<div class="relative
|
|
58
|
+
<div class="relative h-12 w-12 flex-none">
|
|
59
59
|
<img :src="fencer1Portrait" alt="Portrait" class="w-12 h-12 rounded-full" />
|
|
60
|
-
<div :class="`w-4 h-4 rounded-full bg-${card.fencer1.colorDot} absolute
|
|
60
|
+
<div :class="`w-4 h-4 rounded-full bg-${card.fencer1.colorDot} absolute bottom-0 -right-1`"></div>
|
|
61
|
+
<span
|
|
62
|
+
class="pointer-events-none absolute -top-1 left-1/2 z-10 -translate-x-1/2 whitespace-nowrap rounded bg-alarm px-1 py-0.5 text-[9px] font-bold leading-none text-white"
|
|
63
|
+
>
|
|
64
|
+
In progress
|
|
65
|
+
</span>
|
|
61
66
|
</div>
|
|
62
67
|
|
|
63
|
-
<div class="text-center flex-1 mx-
|
|
68
|
+
<div class="text-center flex-1 min-w-0 mx-3">
|
|
69
|
+
<div v-if="$slots['mobile-details']" class="mb-1 min-w-0">
|
|
70
|
+
<slot name="mobile-details" />
|
|
71
|
+
</div>
|
|
64
72
|
<BaseText :text="`${card.score1} - ${card.fencer1.name}`" size="sm" color="quinary" weight="bold" />
|
|
65
73
|
<div>
|
|
66
74
|
<BaseText :text="displayTime" size="xs" color="quaternary" weight="bold" class="-mt-0.5" />
|
|
@@ -68,8 +76,8 @@
|
|
|
68
76
|
<BaseText :text="`${card.score2} - ${card.fencer2.name}`" size="sm" color="quinary" weight="bold" />
|
|
69
77
|
</div>
|
|
70
78
|
|
|
71
|
-
<div class="relative
|
|
72
|
-
<div :class="`w-4 h-4 rounded-full bg-${card.fencer2.colorDot} absolute
|
|
79
|
+
<div class="relative h-12 w-12 flex-none">
|
|
80
|
+
<div :class="`w-4 h-4 rounded-full bg-${card.fencer2.colorDot} absolute bottom-0 -right-1`"></div>
|
|
73
81
|
<img :src="fencer2Portrait" alt="Portrait" class="w-12 h-12 rounded-full" />
|
|
74
82
|
</div>
|
|
75
83
|
</div>
|
|
@@ -101,6 +109,9 @@ export default {
|
|
|
101
109
|
hasCard() {
|
|
102
110
|
return !!this.card;
|
|
103
111
|
},
|
|
112
|
+
mobileCardLayoutClass() {
|
|
113
|
+
return this.$slots['mobile-details'] ? 'border-2' : 'border';
|
|
114
|
+
},
|
|
104
115
|
mobileCardActionClass() {
|
|
105
116
|
if (this.editMode) return 'cursor-pointer';
|
|
106
117
|
return 'cursor-default';
|
|
@@ -131,9 +142,7 @@ export default {
|
|
|
131
142
|
'card.timeLeftSeconds': {
|
|
132
143
|
immediate: true,
|
|
133
144
|
handler(n) {
|
|
134
|
-
|
|
135
|
-
if (this.timerStatus !== 'running') this.remaining = next;
|
|
136
|
-
if (this.timerStatus === 'running' && this.remaining === 0) this.remaining = next;
|
|
145
|
+
this.remaining = Math.max(0, Math.floor(Number(n) || 0));
|
|
137
146
|
}
|
|
138
147
|
},
|
|
139
148
|
timerStatus(n) {
|
|
@@ -59,15 +59,16 @@
|
|
|
59
59
|
|
|
60
60
|
<!-- Mobile -->
|
|
61
61
|
<section
|
|
62
|
-
class="relative flex md:hidden items-center border rounded-md bg-poolBox transition overflow-hidden"
|
|
62
|
+
class="relative flex flex-col md:hidden items-center border rounded-md bg-poolBox transition overflow-hidden"
|
|
63
63
|
:class="mobileCardActionClass"
|
|
64
|
+
@click="onCardClick"
|
|
64
65
|
>
|
|
65
66
|
<!-- main tappable area -->
|
|
66
|
-
<div class="flex flex-1 items-center
|
|
67
|
+
<div class="flex w-full flex-1 items-center px-3 py-2 min-w-0">
|
|
67
68
|
<!-- left portrait or index when reordering -->
|
|
68
|
-
<div v-if="!reorderMode" class="relative
|
|
69
|
+
<div v-if="!reorderMode" class="relative w-12 h-12 flex-none">
|
|
69
70
|
<img :src="fencer1Portrait" alt="Portrait" class="w-12 h-12 rounded-full" />
|
|
70
|
-
<div :class="`w-4 h-4 rounded-full bg-${card.fencer1.colorDot}
|
|
71
|
+
<div :class="`w-4 h-4 rounded-full bg-${card.fencer1.colorDot} absolute bottom-0 -right-1 z-10`"></div>
|
|
71
72
|
</div>
|
|
72
73
|
|
|
73
74
|
<div v-if="reorderMode" class="flex flex-col items-center -mt-3 select-none flex-none">
|
|
@@ -76,8 +77,11 @@
|
|
|
76
77
|
</div>
|
|
77
78
|
|
|
78
79
|
<!-- center -->
|
|
79
|
-
<div class="text-center flex-1 mx-
|
|
80
|
-
<div class="
|
|
80
|
+
<div class="text-center flex-1 mx-3 min-w-0 flex flex-col items-center justify-center leading-none">
|
|
81
|
+
<div v-if="$slots['mobile-details']" class="mb-1 w-full min-w-0">
|
|
82
|
+
<slot name="mobile-details" />
|
|
83
|
+
</div>
|
|
84
|
+
<div class="flex max-w-full items-center justify-center">
|
|
81
85
|
<div :class="`mr-2 bg-${card.fencer1.colorDot} w-3 h-3 flex-none`"></div>
|
|
82
86
|
<BaseText
|
|
83
87
|
:text="c.fencer1.name"
|
|
@@ -90,7 +94,7 @@
|
|
|
90
94
|
|
|
91
95
|
<BaseText text="vs" size="xs" color="quaternary" />
|
|
92
96
|
|
|
93
|
-
<div class="flex items-center justify-center">
|
|
97
|
+
<div class="flex max-w-full items-center justify-center">
|
|
94
98
|
<div :class="`mr-2 bg-${card.fencer2.colorDot} w-3 h-3 flex-none`"></div>
|
|
95
99
|
<BaseText
|
|
96
100
|
:text="c.fencer2.name"
|
|
@@ -1,33 +1,42 @@
|
|
|
1
|
-
import BracketDirectorAssignmentCard from './BracketDirectorAssignmentCard.vue';
|
|
2
|
-
|
|
3
|
-
export default {
|
|
4
|
-
title: 'Organisms/Cards/BracketDirectorAssignmentCard',
|
|
5
|
-
component: BracketDirectorAssignmentCard,
|
|
6
|
-
args: {
|
|
7
|
-
station: {
|
|
8
|
-
RingId: 1,
|
|
9
|
-
RingName: 'Ring 1',
|
|
10
|
-
Color: '#2563eb',
|
|
11
|
-
Director: {
|
|
12
|
-
PersonId: 1001,
|
|
13
|
-
DisplayName: 'Alex Morgan',
|
|
14
|
-
PortraitUrl: ''
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
variant: 'assigned'
|
|
18
|
-
},
|
|
19
|
-
argTypes: {
|
|
20
|
-
station: { control: 'object' },
|
|
21
|
-
variant: { control: 'select', options: ['assigned', 'add'] },
|
|
22
|
-
addLabel: { control: 'text' }
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export const Assigned = {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
1
|
+
import BracketDirectorAssignmentCard from './BracketDirectorAssignmentCard.vue';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
title: 'Organisms/Cards/BracketDirectorAssignmentCard',
|
|
5
|
+
component: BracketDirectorAssignmentCard,
|
|
6
|
+
args: {
|
|
7
|
+
station: {
|
|
8
|
+
RingId: 1,
|
|
9
|
+
RingName: 'Ring 1',
|
|
10
|
+
Color: '#2563eb',
|
|
11
|
+
Director: {
|
|
12
|
+
PersonId: 1001,
|
|
13
|
+
DisplayName: 'Alex Morgan',
|
|
14
|
+
PortraitUrl: ''
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
variant: 'assigned'
|
|
18
|
+
},
|
|
19
|
+
argTypes: {
|
|
20
|
+
station: { control: 'object' },
|
|
21
|
+
variant: { control: 'select', options: ['assigned', 'add'] },
|
|
22
|
+
addLabel: { control: 'text' }
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const Assigned = {
|
|
27
|
+
parameters: {
|
|
28
|
+
docs: {
|
|
29
|
+
description: {
|
|
30
|
+
story:
|
|
31
|
+
'The remove control emits action:remove with a detached station payload. The parent updates planned coverage.'
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const AddDirector = {
|
|
38
|
+
args: {
|
|
39
|
+
station: null,
|
|
40
|
+
variant: 'add'
|
|
41
|
+
}
|
|
42
|
+
};
|
|
@@ -1,45 +1,76 @@
|
|
|
1
|
-
import { shallowMount } from '@vue/test-utils';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import BracketDirectorAssignmentCard from './BracketDirectorAssignmentCard.vue';
|
|
4
|
-
|
|
5
|
-
const station = {
|
|
6
|
-
RingId: 3,
|
|
7
|
-
RingName: 'Ring 3',
|
|
8
|
-
Color: '#059669',
|
|
9
|
-
Director: { PersonId: 1003, DisplayName: 'Sam Rivera', PortraitUrl: '/portrait.png' }
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
describe('BracketDirectorAssignmentCard', () => {
|
|
13
|
-
it('renders the ring, portrait, and director name', () => {
|
|
14
|
-
const wrapper = shallowMount(BracketDirectorAssignmentCard, { props: { station } });
|
|
15
|
-
|
|
16
|
-
expect(wrapper.find('img').attributes()).toMatchObject({
|
|
17
|
-
src: '/portrait.png',
|
|
18
|
-
alt: 'Sam Rivera portrait'
|
|
19
|
-
});
|
|
20
|
-
expect(wrapper.findAll('base-text-stub').map(text => text.attributes('text'))).toContain('Sam Rivera');
|
|
21
|
-
expect(wrapper.find('[data-testid="bracket-director-assignment"]').attributes('style')).toBeUndefined();
|
|
22
|
-
expect(wrapper.find('[data-testid="director-color-dot"]').attributes('style')).toContain('#059669');
|
|
23
|
-
expect(wrapper.find('[data-testid="ring-number-icon"]').exists()).toBe(false);
|
|
24
|
-
expect(wrapper.findAll('base-text-stub').map(text => text.attributes('text'))).toContain('Ring 3');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('uses director initials when no portrait is supplied', () => {
|
|
28
|
-
const wrapper = shallowMount(BracketDirectorAssignmentCard, {
|
|
29
|
-
props: { station: { ...station, Director: { ...station.Director, PortraitUrl: '' } } }
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
expect(wrapper.find('img').exists()).toBe(false);
|
|
33
|
-
expect(wrapper.find('[data-testid="director-initials"]').classes()).toContain('rounded-xl');
|
|
34
|
-
expect(wrapper.find('[data-testid="director-initials"] base-text-stub').attributes('text')).toBe('SR');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('emits add intent from the summary card', async () => {
|
|
38
|
-
const wrapper = shallowMount(BracketDirectorAssignmentCard, { props: { variant: 'add' } });
|
|
39
|
-
|
|
40
|
-
await wrapper.find('[data-testid="add-bracket-director"]').trigger('click');
|
|
41
|
-
|
|
42
|
-
expect(wrapper.emitted('action:add')).toEqual([[]]);
|
|
43
|
-
expect(wrapper.find('base-text-stub').attributes('text')).toBe('Click here to add director');
|
|
44
|
-
});
|
|
45
|
-
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import BracketDirectorAssignmentCard from './BracketDirectorAssignmentCard.vue';
|
|
4
|
+
|
|
5
|
+
const station = {
|
|
6
|
+
RingId: 3,
|
|
7
|
+
RingName: 'Ring 3',
|
|
8
|
+
Color: '#059669',
|
|
9
|
+
Director: { PersonId: 1003, DisplayName: 'Sam Rivera', PortraitUrl: '/portrait.png' }
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
describe('BracketDirectorAssignmentCard', () => {
|
|
13
|
+
it('renders the ring, portrait, and director name', () => {
|
|
14
|
+
const wrapper = shallowMount(BracketDirectorAssignmentCard, { props: { station } });
|
|
15
|
+
|
|
16
|
+
expect(wrapper.find('img').attributes()).toMatchObject({
|
|
17
|
+
src: '/portrait.png',
|
|
18
|
+
alt: 'Sam Rivera portrait'
|
|
19
|
+
});
|
|
20
|
+
expect(wrapper.findAll('base-text-stub').map(text => text.attributes('text'))).toContain('Sam Rivera');
|
|
21
|
+
expect(wrapper.find('[data-testid="bracket-director-assignment"]').attributes('style')).toBeUndefined();
|
|
22
|
+
expect(wrapper.find('[data-testid="director-color-dot"]').attributes('style')).toContain('#059669');
|
|
23
|
+
expect(wrapper.find('[data-testid="ring-number-icon"]').exists()).toBe(false);
|
|
24
|
+
expect(wrapper.findAll('base-text-stub').map(text => text.attributes('text'))).toContain('Ring 3');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('uses director initials when no portrait is supplied', () => {
|
|
28
|
+
const wrapper = shallowMount(BracketDirectorAssignmentCard, {
|
|
29
|
+
props: { station: { ...station, Director: { ...station.Director, PortraitUrl: '' } } }
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(wrapper.find('img').exists()).toBe(false);
|
|
33
|
+
expect(wrapper.find('[data-testid="director-initials"]').classes()).toContain('rounded-xl');
|
|
34
|
+
expect(wrapper.find('[data-testid="director-initials"] base-text-stub').attributes('text')).toBe('SR');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('emits add intent from the summary card', async () => {
|
|
38
|
+
const wrapper = shallowMount(BracketDirectorAssignmentCard, { props: { variant: 'add' } });
|
|
39
|
+
|
|
40
|
+
await wrapper.find('[data-testid="add-bracket-director"]').trigger('click');
|
|
41
|
+
|
|
42
|
+
expect(wrapper.emitted('action:add')).toEqual([[]]);
|
|
43
|
+
expect(wrapper.find('base-text-stub').attributes('text')).toBe('Click here to add director');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('emits a detached station when removing a director and waits for updated props', async () => {
|
|
47
|
+
const selectedStation = structuredClone(station);
|
|
48
|
+
const wrapper = shallowMount(BracketDirectorAssignmentCard, { props: { station: selectedStation } });
|
|
49
|
+
const removeButton = wrapper.get('[data-testid="remove-bracket-director"]');
|
|
50
|
+
expect(removeButton.attributes('aria-label')).toBe('Remove Sam Rivera from Ring 3');
|
|
51
|
+
await removeButton.trigger('click');
|
|
52
|
+
const payload = wrapper.emitted('action:remove')[0][0];
|
|
53
|
+
expect(payload).toEqual(selectedStation);
|
|
54
|
+
expect(payload).not.toBe(selectedStation);
|
|
55
|
+
payload.Director.DisplayName = 'Changed';
|
|
56
|
+
expect(selectedStation.Director.DisplayName).toBe('Sam Rivera');
|
|
57
|
+
expect(wrapper.find('[data-testid="bracket-director-assignment"]').exists()).toBe(true);
|
|
58
|
+
await wrapper.setProps({ station: null });
|
|
59
|
+
expect(wrapper.find('[data-testid="bracket-director-assignment"]').exists()).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('has no remove control on the add card or an empty card', () => {
|
|
63
|
+
const addCard = shallowMount(BracketDirectorAssignmentCard, { props: { variant: 'add' } });
|
|
64
|
+
const emptyCard = shallowMount(BracketDirectorAssignmentCard);
|
|
65
|
+
expect(addCard.find('[data-testid="remove-bracket-director"]').exists()).toBe(false);
|
|
66
|
+
expect(emptyCard.find('[data-testid="remove-bracket-director"]').exists()).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('displays the ring as text and reflects parent updates', async () => {
|
|
70
|
+
const wrapper = shallowMount(BracketDirectorAssignmentCard, { props: { station } });
|
|
71
|
+
expect(wrapper.find('select').exists()).toBe(false);
|
|
72
|
+
expect(wrapper.findAll('base-text-stub').map(text => text.attributes('text'))).toContain('Ring 3');
|
|
73
|
+
await wrapper.setProps({ station: { ...station, RingId: 1, RingName: 'Ring 1' } });
|
|
74
|
+
expect(wrapper.findAll('base-text-stub').map(text => text.attributes('text'))).toContain('Ring 1');
|
|
75
|
+
});
|
|
76
|
+
});
|