@dcrackel/meyersquaredui 1.0.115 → 1.0.117
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/dist/meyersquaredui.es.js +808 -709
- package/dist/meyersquaredui.umd.js +4 -4
- package/package.json +1 -1
- package/src/index.js +3 -2
- package/src/mocks/getPoolsWithBouts.js +413 -0
- package/src/mocks/getPoolsWithBoutsByPoolId.js +1178 -0
- package/src/stories/Molecules/BreadCrumbs/BreadCrumbs.stories.js +22 -0
- package/src/stories/Molecules/BreadCrumbs/BreadCrumbs.vue +57 -0
- package/src/stories/Molecules/ProgressTracker/ProgressTracker.stories.js +3 -2
- package/src/stories/Molecules/ProgressTracker/ProgressTracker.vue +14 -9
- package/src/stories/Organisms/Cards/BoutCard/BoutCard.stories.js +81 -0
- package/src/stories/Organisms/Cards/BoutCard/BoutCard.vue +199 -0
- package/src/stories/Organisms/{GridLayout → Grids/GridLayout}/GridLayout.stories.js +6 -6
- package/src/stories/Organisms/{GridLayout → Grids/GridLayout}/GridLayout.vue +3 -3
- package/src/stories/Organisms/Grids/GridPool/GridPool.stories.js +30 -0
- package/src/stories/Organisms/Grids/GridPool/GridPool.vue +195 -0
- package/src/stories/Organisms/{GridTabs → Grids/GridTabs}/GridTabs.stories.js +2 -2
- package/src/stories/Organisms/{GridTabs → Grids/GridTabs}/GridTabs.vue +5 -5
- package/src/stories/Templates/ClubDetailPage/ClubDetailPage.vue +1 -1
- package/src/stories/Templates/ClubListPage/ClubListPage.vue +1 -1
- package/src/stories/Templates/HomePage/HomePage.vue +1 -1
- package/src/stories/Templates/Leaderboard/Leaderboard.vue +1 -1
- package/src/stories/Templates/TournamentDetailPage/TournamentDetailPage.vue +1 -1
- package/src/stories/Templates/TournamentListPage/TournamentListPage.vue +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import BreadCrumbs from './BreadCrumbs.vue';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
title: 'Molecules/BreadCrumbs/BreadCrumbs',
|
|
5
|
+
component: BreadCrumbs,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const Template = (args) => ({
|
|
9
|
+
components: { BreadCrumbs },
|
|
10
|
+
setup() {
|
|
11
|
+
return { args };
|
|
12
|
+
},
|
|
13
|
+
template: '<BreadCrumbs v-bind="args" />',
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const Default = Template.bind({});
|
|
17
|
+
Default.args = {
|
|
18
|
+
tournamentName: 'Spring Tournament',
|
|
19
|
+
eventName: 'Saber Fights',
|
|
20
|
+
currentStep: 'Registration',
|
|
21
|
+
detailMessage: 'Click on the tournament or event to view more details.',
|
|
22
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-col mb-20 lg:ml-4">
|
|
3
|
+
<div class="flex overflow-x-auto md:overflow-x-hidden mx-2 md:ml-0 h-12">
|
|
4
|
+
<BaseText tag="h2" weight="bold"
|
|
5
|
+
class="mt-4 underline hover:text-accent text-sm md:text-xl whitespace-nowrap"
|
|
6
|
+
@click="navigateToTournamentDetail">{{ tournamentName }}
|
|
7
|
+
</BaseText>
|
|
8
|
+
<Icon icon="fa-chevron-right" color="primary" size="sm" class="ml-4 mt-4 md:mt-5"/>
|
|
9
|
+
<BaseText tag="h2" weight="bold" class="pb-0.5 ml-4 mt-4 text-sm md:text-xl whitespace-nowrap">
|
|
10
|
+
{{ eventName }}
|
|
11
|
+
</BaseText>
|
|
12
|
+
<Icon icon="fa-chevron-right" color="primary" size="sm" class="ml-4 mt-4 md:mt-5"/>
|
|
13
|
+
<BaseText tag="h2" weight="bold" class="pb-0.5 ml-4 mt-4 text-sm md:text-xl whitespace-nowrap">
|
|
14
|
+
{{ currentStep }}
|
|
15
|
+
</BaseText>
|
|
16
|
+
</div>
|
|
17
|
+
<BaseText tag="h2" weight="regular" class="mt-2 mb-8 mx-2 md:mx-0 text-sm md:text-md md:ml-0">
|
|
18
|
+
{{ detailMessage }}
|
|
19
|
+
</BaseText>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script>
|
|
24
|
+
import BaseText from '../../Atoms/BaseText/BaseText.vue';
|
|
25
|
+
import Icon from '../../Atoms/Icon/Icon.vue';
|
|
26
|
+
|
|
27
|
+
export default {
|
|
28
|
+
name: 'BreadCrumbs',
|
|
29
|
+
components: {
|
|
30
|
+
BaseText,
|
|
31
|
+
Icon,
|
|
32
|
+
},
|
|
33
|
+
props: {
|
|
34
|
+
tournamentName: {
|
|
35
|
+
type: String,
|
|
36
|
+
required: true
|
|
37
|
+
},
|
|
38
|
+
eventName: {
|
|
39
|
+
type: String,
|
|
40
|
+
required: true
|
|
41
|
+
},
|
|
42
|
+
currentStep: {
|
|
43
|
+
type: String,
|
|
44
|
+
required: true
|
|
45
|
+
},
|
|
46
|
+
detailMessage: {
|
|
47
|
+
type: String,
|
|
48
|
+
default: ''
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
methods: {
|
|
52
|
+
navigateToTournamentDetail() {
|
|
53
|
+
this.$emit('navigate');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
</script>
|
|
@@ -22,10 +22,11 @@ export const Default = Template.bind({});
|
|
|
22
22
|
Default.args = {
|
|
23
23
|
currentPhase: 2, // Example: Pool Results
|
|
24
24
|
highlightPhase: 1, // Example: Pools (User viewing Pools page)
|
|
25
|
-
|
|
25
|
+
livePhase: 1,
|
|
26
|
+
isEventComplete: false, //fa-kit fa-longsword
|
|
26
27
|
phases: [
|
|
27
28
|
{ label: 'Registration', icon: 'fa-solid fa-file-alt' },
|
|
28
|
-
{ label: 'Pools', icon: 'fa-
|
|
29
|
+
{ label: 'Pools', icon: 'fa-regular fa-circle-dot' },
|
|
29
30
|
{ label: 'Pool Results', icon: 'fa-solid fa-clipboard-check' },
|
|
30
31
|
{ label: 'Bracket', icon: 'fa-solid fa-sitemap' },
|
|
31
32
|
{ label: 'Final Result', icon: 'fa-solid fa-trophy' },
|
|
@@ -15,16 +15,17 @@
|
|
|
15
15
|
<!-- Clickable Icon and Label -->
|
|
16
16
|
<div class="flex flex-col items-center"
|
|
17
17
|
:class="{'hover:cursor-pointer': index <= currentPhase, 'hover:cursor-default': index > currentPhase}">
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
<div
|
|
19
|
+
class="w-6 h-6 md:w-9 md:h-9 flex items-center justify-center rounded-full transition-all duration-300 border-2"
|
|
20
|
+
:class="{
|
|
21
|
+
'border-accent': isPhaseActive(index),
|
|
22
|
+
'border-gray-300': !isPhaseActive(index),
|
|
23
|
+
'hover:bg-accent': hoveredPhase === index && index <= currentPhase,
|
|
24
|
+
'hover:bg-gray-200': hoveredPhase === index && !isPhaseActive(index) && index <= currentPhase,
|
|
25
|
+
}"
|
|
26
|
+
>
|
|
27
27
|
<i :class="[phase.icon, 'text-xs md:text-lg']"></i>
|
|
28
|
+
<i v-if="livePhase === index" :class="[phase.icon, 'text-xs md:text-lg absolute animate-ping text-accent']"></i>
|
|
28
29
|
</div>
|
|
29
30
|
<BaseText
|
|
30
31
|
tag="p"
|
|
@@ -74,6 +75,10 @@ export default {
|
|
|
74
75
|
type: Number,
|
|
75
76
|
default: null,
|
|
76
77
|
},
|
|
78
|
+
livePhase: {
|
|
79
|
+
type: Number,
|
|
80
|
+
default: null,
|
|
81
|
+
},
|
|
77
82
|
isEventComplete: {
|
|
78
83
|
type: Boolean,
|
|
79
84
|
default: false,
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import BoutCard from './BoutCard.vue';
|
|
2
|
+
import getPoolsWithBouts from '../../../../mocks/getPoolsWithBouts.js';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Organisms/Cards/BoutCard',
|
|
6
|
+
component: BoutCard,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
args: {
|
|
9
|
+
bout: getPoolsWithBouts[0],
|
|
10
|
+
isWaiting: false,
|
|
11
|
+
timerStatus: 'stopped',
|
|
12
|
+
hostingClubColors: {
|
|
13
|
+
Color1: 'red',
|
|
14
|
+
Color2: 'blue'
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
argTypes: {
|
|
18
|
+
bout: {
|
|
19
|
+
control: 'object'
|
|
20
|
+
},
|
|
21
|
+
isSkeleton: {
|
|
22
|
+
control: 'boolean'
|
|
23
|
+
},
|
|
24
|
+
timerStatus: {
|
|
25
|
+
control: 'text'
|
|
26
|
+
},
|
|
27
|
+
hostingClubColors: {
|
|
28
|
+
control: 'object'
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
export const Default = {
|
|
36
|
+
args: {
|
|
37
|
+
bout: getPoolsWithBouts[0],
|
|
38
|
+
isWaiting: false,
|
|
39
|
+
timerStatus: 'running',
|
|
40
|
+
hostingClubColors: {
|
|
41
|
+
Color1: 'red',
|
|
42
|
+
Color2: 'blue'
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const Scheduled = {
|
|
48
|
+
args: {
|
|
49
|
+
bout: getPoolsWithBouts[4],
|
|
50
|
+
isWaiting: false,
|
|
51
|
+
timerStatus: 'stopped',
|
|
52
|
+
hostingClubColors: {
|
|
53
|
+
Color1: 'red',
|
|
54
|
+
Color2: 'blue'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const Completed = {
|
|
60
|
+
args: {
|
|
61
|
+
bout: getPoolsWithBouts[2],
|
|
62
|
+
isWaiting: false,
|
|
63
|
+
timerStatus: 'stopped',
|
|
64
|
+
hostingClubColors: {
|
|
65
|
+
Color1: 'red',
|
|
66
|
+
Color2: 'blue'
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const Skeleton = {
|
|
72
|
+
args: {
|
|
73
|
+
bout: getPoolsWithBouts[2],
|
|
74
|
+
isWaiting: true,
|
|
75
|
+
timerStatus: 'stopped',
|
|
76
|
+
hostingClubColors: {
|
|
77
|
+
Color1: 'red',
|
|
78
|
+
Color2: 'blue'
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section>
|
|
3
|
+
<div v-if="isWaiting" class="flex items-center justify-between rounded-lg w-full">
|
|
4
|
+
<!-- Fencer 1 -->
|
|
5
|
+
<div class="flex justify-between shadow-lg drop-shadow p-2 rounded-xl bg-poolBox w-5/12">
|
|
6
|
+
<div class="flex space-x-4 items-center">
|
|
7
|
+
<div>
|
|
8
|
+
<div :class="`w-4 h-4 rounded-full bg-${hostingClubColors.Color1} absolute top-10 left-11 `"></div>
|
|
9
|
+
<img :src="missingPortrait" alt="Portrait" class="w-12 h-12 rounded-full"/>
|
|
10
|
+
</div>
|
|
11
|
+
<div>
|
|
12
|
+
<BaseText size="md" color="quaternary" weight="bold">Waiting to be selected</BaseText>
|
|
13
|
+
<BaseText size="xs" color="quinary">starting soon</BaseText>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
<BaseText text="" size="3xl" weight="bold" :class="`align-middle pr-3 pt-0.5`"/>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<!-- Timer -->
|
|
20
|
+
<div class="text-center mx-8"></div>
|
|
21
|
+
|
|
22
|
+
<!-- Fencer 2 -->
|
|
23
|
+
<div class="flex justify-between shadow-lg drop-shadow p-2 rounded-xl bg-poolBox w-5/12">
|
|
24
|
+
<div class="flex justify-end space-x-4 ml-4 w-full">
|
|
25
|
+
<div>
|
|
26
|
+
<BaseText size="md" color="quaternary" weight="bold" class="text-right">Waiting to be selected</BaseText>
|
|
27
|
+
<BaseText size="xs" color="quinary" class="text-right">starting soon</BaseText>
|
|
28
|
+
</div>
|
|
29
|
+
<div>
|
|
30
|
+
<div :class="`w-4 h-4 rounded-full bg-${hostingClubColors.Color2} absolute top-10 right-12`"></div>
|
|
31
|
+
<img :src="missingPortrait" alt="Portrait" class="w-12 h-12 rounded-full"/>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<!-- Custom Status -->
|
|
38
|
+
<div v-if="bout.customStatus && bout.Status !== 'Active'" class="text-left">
|
|
39
|
+
<BaseText size="sm" color="primary">{{bout.customStatus}}</BaseText>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<!-- Bout Card -->
|
|
43
|
+
<div v-if="!isWaiting" class="flex items-center justify-between rounded-lg">
|
|
44
|
+
<!-- Fencer 1 -->
|
|
45
|
+
<div class="w-5/12 flex justify-between border-l border-t border-b p-2 bg-poolBox rounded-l-xl" :class="fencer1Class">
|
|
46
|
+
<div class="flex space-x-4 items-center">
|
|
47
|
+
<div>
|
|
48
|
+
<div :class="`w-4 h-4 rounded-full bg-${hostingClubColors.Color1} fixed mt-8 ml-9`"></div>
|
|
49
|
+
<img :src="fencer1.portrait" alt="Portrait" class="w-12 h-12 rounded-full"/>
|
|
50
|
+
</div>
|
|
51
|
+
<div>
|
|
52
|
+
<BaseText size="md" color="quaternary" weight="bold">{{fencer1.name}}</BaseText>
|
|
53
|
+
<BaseText size="xs" color="quinary">{{fencer1.team}}</BaseText>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
<BaseText size="3xl" weight="bold" :class="`align-middle pr-3 pt-0.5`">{{fencer1Score}}</BaseText>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<!-- Timer -->
|
|
60
|
+
<div v-if="bout.Status === 'Active'" class="w-2/12 h-16 pt-3 text-center bg-primary">
|
|
61
|
+
<BaseText size="2xl" color="white" weight="bold">{{displayTime}}</BaseText>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<!-- Fencer 2 -->
|
|
65
|
+
<div class="w-5/12 flex justify-between border-r border-t border-b p-2 bg-poolBox rounded-r-xl" :class="fencer2Class">
|
|
66
|
+
<div class="flex space-x-4 justify-between w-full">
|
|
67
|
+
<BaseText size="3xl" weight="bold" :class="`align-middle pl-3 pt-0.5`">{{fencer2Score}}</BaseText>
|
|
68
|
+
<div class="w-full">
|
|
69
|
+
<BaseText size="md" color="quaternary" weight="bold" class="text-right">{{fencer2.name}}</BaseText>
|
|
70
|
+
<BaseText size="xs" color="quinary" class="text-right">{{fencer2.team}}</BaseText>
|
|
71
|
+
</div>
|
|
72
|
+
<div class="w-20">
|
|
73
|
+
<div :class="`w-4 h-4 rounded-full bg-${hostingClubColors.Color2} fixed mt-8 mr-14`"></div>
|
|
74
|
+
<img :src="fencer2.portrait" alt="Portrait" class="w-12 h-12 rounded-full"/>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
</div>
|
|
80
|
+
</section>
|
|
81
|
+
</template>
|
|
82
|
+
|
|
83
|
+
<script>
|
|
84
|
+
import missingPortrait from "../../../../assets/images/portrait1.png";
|
|
85
|
+
import BaseText from "../../../Atoms/BaseText/BaseText.vue";
|
|
86
|
+
import BaseButton from "../../../Atoms/BaseButton/BaseButton.vue";
|
|
87
|
+
|
|
88
|
+
export default {
|
|
89
|
+
components: {BaseButton, BaseText},
|
|
90
|
+
props: {
|
|
91
|
+
bout: {
|
|
92
|
+
type: Object,
|
|
93
|
+
required: true
|
|
94
|
+
},
|
|
95
|
+
isWaiting: {
|
|
96
|
+
type: Boolean,
|
|
97
|
+
required: false,
|
|
98
|
+
default: false
|
|
99
|
+
},
|
|
100
|
+
timerStatus: {
|
|
101
|
+
type: String,
|
|
102
|
+
required: false,
|
|
103
|
+
default: 'stopped'
|
|
104
|
+
},
|
|
105
|
+
hostingClubColors: {
|
|
106
|
+
type: Object,
|
|
107
|
+
required: true
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
data() {
|
|
111
|
+
return {
|
|
112
|
+
remainingTime: this.bout.TimeLeft,
|
|
113
|
+
interval: null,
|
|
114
|
+
missingPortrait
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
mounted() {
|
|
118
|
+
if (this.timerStatus === 'running') {
|
|
119
|
+
this.startTimer();
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
beforeDestroy() {
|
|
123
|
+
this.stopTimer();
|
|
124
|
+
},
|
|
125
|
+
computed: {
|
|
126
|
+
fencer1() {
|
|
127
|
+
return {
|
|
128
|
+
portrait: this.bout.Person1.Images.length ? this.bout.Person1.Images[0].URL : this.missingPortrait,
|
|
129
|
+
name: this.bout.Person1.DisplayName,
|
|
130
|
+
team: this.bout.Person1.Club.Name,
|
|
131
|
+
score: this.bout.Score1
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
fencer2() {
|
|
135
|
+
return {
|
|
136
|
+
portrait: this.bout.Person2.Images.length ? this.bout.Person2.Images[0].URL : this.missingPortrait,
|
|
137
|
+
name: this.bout.Person2.DisplayName,
|
|
138
|
+
team: this.bout.Person2.Club.Name,
|
|
139
|
+
score: this.bout.Score2
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
fencer1Score() {
|
|
143
|
+
return this.bout.Status !== 'Scheduled' ? this.fencer1.score : '-';
|
|
144
|
+
},
|
|
145
|
+
fencer2Score() {
|
|
146
|
+
return this.bout.Status !== 'Scheduled' ? this.fencer2.score : '-';
|
|
147
|
+
},
|
|
148
|
+
displayTime() {
|
|
149
|
+
const minutes = Math.floor(this.remainingTime / 60).toString().padStart(2, '0');
|
|
150
|
+
const seconds = (this.remainingTime % 60).toString().padStart(2, '0');
|
|
151
|
+
return `${minutes}:${seconds}`;
|
|
152
|
+
},
|
|
153
|
+
fencer1Class() {
|
|
154
|
+
return {
|
|
155
|
+
[`border-${this.hostingClubColors.Color1}`]: this.bout.Status === 'Active',
|
|
156
|
+
[`border-r`]: this.bout.Status !== 'Active',
|
|
157
|
+
};
|
|
158
|
+
},
|
|
159
|
+
fencer2Class() {
|
|
160
|
+
return {
|
|
161
|
+
[`border-${this.hostingClubColors.Color2}`]: this.bout.Status === 'Active',
|
|
162
|
+
[`border-l`]: this.bout.Status !== 'Active',
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
watch: {
|
|
167
|
+
timerStatus(newStatus) {
|
|
168
|
+
if (newStatus === 'running') {
|
|
169
|
+
this.startTimer();
|
|
170
|
+
} else {
|
|
171
|
+
this.stopTimer();
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
bout: {
|
|
175
|
+
deep: true,
|
|
176
|
+
handler(newBout) {
|
|
177
|
+
this.remainingTime = newBout.TimeLeft;
|
|
178
|
+
newBout.TimerStatus === 'running' ? this.startTimer() : this.stopTimer();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
methods: {
|
|
183
|
+
startTimer() {
|
|
184
|
+
if (this.interval) clearInterval(this.interval);
|
|
185
|
+
this.interval = setInterval(() => {
|
|
186
|
+
if (this.remainingTime > 0) {
|
|
187
|
+
this.remainingTime--;
|
|
188
|
+
} else {
|
|
189
|
+
this.stopTimer();
|
|
190
|
+
}
|
|
191
|
+
}, 1000);
|
|
192
|
+
},
|
|
193
|
+
stopTimer() {
|
|
194
|
+
if (this.interval) clearInterval(this.interval);
|
|
195
|
+
this.interval = null;
|
|
196
|
+
},
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
</script>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import GridLayout from './GridLayout.vue';
|
|
2
|
-
import TournamentCard from '../../
|
|
3
|
-
import FencerCard from '../../
|
|
4
|
-
import ArticleCard from '../../
|
|
5
|
-
import mockTournaments from "
|
|
6
|
-
import mockTopFencers from "
|
|
7
|
-
import mockArticles from "
|
|
2
|
+
import TournamentCard from '../../Cards/TournamentCard/TournamentCard.vue';
|
|
3
|
+
import FencerCard from '../../Cards/FencerCard/FencerCard.vue';
|
|
4
|
+
import ArticleCard from '../../Cards/ArticleCard/ArticleCard.vue';
|
|
5
|
+
import mockTournaments from "../../../../mocks/getTournamentsMock.js";
|
|
6
|
+
import mockTopFencers from "../../../../mocks/getTopFencers.js";
|
|
7
|
+
import mockArticles from "../../../../mocks/getArticles.js";
|
|
8
8
|
|
|
9
9
|
export default {
|
|
10
10
|
title: 'Organisms/Grids/GridLayout',
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
</template>
|
|
39
39
|
|
|
40
40
|
<script>
|
|
41
|
-
import BaseButton from "
|
|
42
|
-
import BaseText from "
|
|
41
|
+
import BaseButton from "../../../Atoms/BaseButton/BaseButton.vue";
|
|
42
|
+
import BaseText from "../../../Atoms/BaseText/BaseText.vue";
|
|
43
43
|
import {markRaw} from "vue";
|
|
44
|
-
import Icon from "
|
|
44
|
+
import Icon from "../../../Atoms/Icon/Icon.vue";
|
|
45
45
|
|
|
46
46
|
export default {
|
|
47
47
|
name: 'GridLayout',
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import PoolGrid from './GridPool.vue';
|
|
2
|
+
import getPoolsWithBoutsByPoolId from "../../../../mocks/getPoolsWithBoutsByPoolId.js";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: 'Organisms/Grids/GridPool',
|
|
6
|
+
component: PoolGrid,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
args: {
|
|
9
|
+
bouts: getPoolsWithBoutsByPoolId.pools[0].Bouts
|
|
10
|
+
},
|
|
11
|
+
argTypes: {
|
|
12
|
+
bouts: {
|
|
13
|
+
control: 'object'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const Default = {
|
|
20
|
+
args: {
|
|
21
|
+
bouts: getPoolsWithBoutsByPoolId.pools[0].Bouts
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
export const EmptyPool = {
|
|
27
|
+
args: {
|
|
28
|
+
bouts: []
|
|
29
|
+
}
|
|
30
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex w-full justify-center mb-4 ">
|
|
3
|
+
<table class="border-separate border-spacing-1 w-[900px]">
|
|
4
|
+
<thead>
|
|
5
|
+
<tr>
|
|
6
|
+
<th class="border border-primary bg-gray-500 rounded-lg px-4 py-2 ">
|
|
7
|
+
<BaseText size="sm" weight="bold" color="white">Name</BaseText>
|
|
8
|
+
</th>
|
|
9
|
+
<th class="w-1"></th>
|
|
10
|
+
<th class="bg-gray-300 rounded-lg px-4 py-2"></th>
|
|
11
|
+
<th v-for="(participant, index) in participants" :key="participant.PersonId"
|
|
12
|
+
class="border border-primary bg-gray-500 rounded-lg text-center align-middle">
|
|
13
|
+
<BaseText size="sm" weight="bold" color="white">{{index+1}}</BaseText>
|
|
14
|
+
</th>
|
|
15
|
+
<th class="w-1"></th>
|
|
16
|
+
<th class="border border-primary bg-gray-500 rounded-lg table-cell text-center align-middle h-10 w-10">
|
|
17
|
+
<BaseText size="sm" weight="bold" color="white">W%</BaseText>
|
|
18
|
+
</th>
|
|
19
|
+
<th class="border border-primary bg-gray-500 rounded-lg table-cell text-center align-middle h-10 w-10">
|
|
20
|
+
<BaseText size="sm" weight="bold" color="white">W</BaseText>
|
|
21
|
+
</th>
|
|
22
|
+
<th class="border border-primary bg-gray-500 rounded-lg table-cell text-center align-middle h-10 w-10">
|
|
23
|
+
<BaseText size="sm" weight="bold" color="white">L</BaseText>
|
|
24
|
+
</th>
|
|
25
|
+
<th class="border border-primary bg-gray-500 rounded-lg table-cell text-center align-middle h-10 w-10">
|
|
26
|
+
<BaseText size="sm" weight="bold" color="white">HS</BaseText>
|
|
27
|
+
</th>
|
|
28
|
+
<th class="border border-primary bg-gray-500 rounded-lg table-cell text-center align-middle h-10 w-10">
|
|
29
|
+
<BaseText size="sm" weight="bold" color="white">HR</BaseText>
|
|
30
|
+
</th>
|
|
31
|
+
<th class="border border-primary bg-gray-500 rounded-lg able-cell text-center align-middle h-10 w-10">
|
|
32
|
+
<BaseText size="sm" weight="bold" color="white">IND</BaseText>
|
|
33
|
+
</th>
|
|
34
|
+
</tr>
|
|
35
|
+
</thead>
|
|
36
|
+
<tbody>
|
|
37
|
+
<tr v-for="(participant, index) in participants" :key="participant.PersonId" class="even:bg-gray-100">
|
|
38
|
+
<td class="border border-primary rounded-lg text-right align-middle pr-4">
|
|
39
|
+
<BaseText size="sm" color="primary">{{participant.DisplayName}}</BaseText>
|
|
40
|
+
</td>
|
|
41
|
+
<th class="w-1"></th>
|
|
42
|
+
<td class="border border-primary bg-gray-500 rounded-lg text-center align-middle h-10 w-10">
|
|
43
|
+
<BaseText size="sm" weight="bold" color="white">{{index + 1}}</BaseText>
|
|
44
|
+
</td>
|
|
45
|
+
|
|
46
|
+
<td v-for="opponent in participants" :key="opponent.PersonId"
|
|
47
|
+
:class="{'bg-gray-300 rounded-lg ': participant.PersonId === opponent.PersonId, 'bg-secondary rounded-lg': participant.PersonId !== opponent.PersonId}"
|
|
48
|
+
class="rounded-lg h-10 w-10">
|
|
49
|
+
<div v-if="participant.PersonId !== opponent.PersonId"
|
|
50
|
+
:class="getBoutClass(participant.PersonId, opponent.PersonId)"
|
|
51
|
+
class="rounded-lg text-center pt-2.5 h-10 w-10">
|
|
52
|
+
<BaseText size="sm" color="white">{{getScore(participant.PersonId, opponent.PersonId)}}</BaseText>
|
|
53
|
+
</div>
|
|
54
|
+
</td>
|
|
55
|
+
|
|
56
|
+
<td class="w-1"></td>
|
|
57
|
+
<td class="border border-primary rounded-lg table-cell text-center align-middle h-10 w-10">
|
|
58
|
+
<BaseText size="sm" color="primary" >{{getStats(participant).WP}}</BaseText>
|
|
59
|
+
</td>
|
|
60
|
+
<td class="border border-primary rounded-lg table-cell text-center align-middle h-10 w-10">
|
|
61
|
+
<BaseText size="sm" color="primary">{{getStats(participant).W}}</BaseText>
|
|
62
|
+
</td>
|
|
63
|
+
<td class="border border-primary rounded-lg table-cell text-center align-middle h-10 w-10">
|
|
64
|
+
<BaseText size="sm" color="primary">{{getStats(participant).L}}</BaseText>
|
|
65
|
+
</td>
|
|
66
|
+
<td class="border border-primary rounded-lg table-cell text-center align-middle h-10 w-10">
|
|
67
|
+
<BaseText size="sm" color="primary">{{getStats(participant).HS}}</BaseText>
|
|
68
|
+
</td>
|
|
69
|
+
<td class="border border-primary rounded-lg table-cell text-center align-middle h-10 w-10">
|
|
70
|
+
<BaseText size="sm" color="primary">{{getStats(participant).HR}}</BaseText>
|
|
71
|
+
</td>
|
|
72
|
+
<td class="border border-primary rounded-lg table-cell text-center align-middle h-10 w-10">
|
|
73
|
+
<BaseText size="sm" color="primary">{{getStats(participant).IND}}</BaseText>
|
|
74
|
+
</td>
|
|
75
|
+
</tr>
|
|
76
|
+
</tbody>
|
|
77
|
+
</table>
|
|
78
|
+
|
|
79
|
+
</div>
|
|
80
|
+
</template>
|
|
81
|
+
|
|
82
|
+
<script>
|
|
83
|
+
import BaseText from "../../../Atoms/BaseText/BaseText.vue";
|
|
84
|
+
|
|
85
|
+
export default {
|
|
86
|
+
name: 'GridPool',
|
|
87
|
+
components: {BaseText},
|
|
88
|
+
props: {
|
|
89
|
+
bouts: {
|
|
90
|
+
type: Array,
|
|
91
|
+
required: true,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
data() {
|
|
95
|
+
return {};
|
|
96
|
+
},
|
|
97
|
+
computed: {
|
|
98
|
+
participants() {
|
|
99
|
+
const participantMap = {};
|
|
100
|
+
this.bouts.forEach((bout) => {
|
|
101
|
+
participantMap[bout.Person1Id] = bout.Person1;
|
|
102
|
+
participantMap[bout.Person2Id] = bout.Person2;
|
|
103
|
+
});
|
|
104
|
+
return Object.values(participantMap);
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
methods: {
|
|
108
|
+
getScore(rowPersonId, colPersonId) {
|
|
109
|
+
const bout = this.bouts.find(
|
|
110
|
+
(bout) =>
|
|
111
|
+
(bout.Person1Id === rowPersonId && bout.Person2Id === colPersonId) ||
|
|
112
|
+
(bout.Person1Id === colPersonId && bout.Person2Id === rowPersonId)
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (bout) {
|
|
116
|
+
if (bout.Status === 'Scheduled') return '';
|
|
117
|
+
|
|
118
|
+
const score1 = bout.Score1;
|
|
119
|
+
const score2 = bout.Score2;
|
|
120
|
+
|
|
121
|
+
if (bout.Status === 'Completed') {
|
|
122
|
+
if (score1 > score2) {
|
|
123
|
+
return bout.Person1Id === rowPersonId
|
|
124
|
+
? `W${score1}`
|
|
125
|
+
: `L${score2}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (score2 > score1) {
|
|
129
|
+
return bout.Person1Id === rowPersonId
|
|
130
|
+
? `L${score1}`
|
|
131
|
+
: `W${score2}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return `T${score1}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return bout.Person1Id === rowPersonId ? score1 : score2;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return '';
|
|
141
|
+
},
|
|
142
|
+
getBoutClass(person1Id, person2Id) {
|
|
143
|
+
const bout = this.bouts.find((bout) => (bout.Person1Id === person1Id && bout.Person2Id === person2Id) || (bout.Person1Id === person2Id && bout.Person2Id === person1Id));
|
|
144
|
+
|
|
145
|
+
if (bout) {
|
|
146
|
+
if (bout.Status === 'Completed') {
|
|
147
|
+
if (bout.Score1 > bout.Score2) {
|
|
148
|
+
return person1Id === bout.Person1Id ? 'bg-accent' : 'bg-gray-400';
|
|
149
|
+
}
|
|
150
|
+
if (bout.Score2 > bout.Score1) {
|
|
151
|
+
return person2Id === bout.Person2Id ? 'bg-gray-400' : 'bg-accent';
|
|
152
|
+
}
|
|
153
|
+
if (bout.Score2 === bout.Score1) {
|
|
154
|
+
return person2Id === bout.Person2Id ? 'bg-primary' : 'bg-primary';
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (bout.Status === 'Active') {
|
|
159
|
+
return 'bg-blue';
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return '';
|
|
163
|
+
},
|
|
164
|
+
getStats(participant) {
|
|
165
|
+
const completedBouts = this.bouts.filter((bout) =>
|
|
166
|
+
(bout.Person1Id === participant.PersonId || bout.Person2Id === participant.PersonId) &&
|
|
167
|
+
bout.Status === 'Completed');
|
|
168
|
+
|
|
169
|
+
const wins = completedBouts.filter((bout) =>
|
|
170
|
+
(bout.Person1Id === participant.PersonId && bout.Score1 > bout.Score2) ||
|
|
171
|
+
(bout.Person2Id === participant.PersonId && bout.Score2 > bout.Score1)).length;
|
|
172
|
+
|
|
173
|
+
const losses = completedBouts.filter((bout) =>
|
|
174
|
+
(bout.Person1Id === participant.PersonId && bout.Score1 < bout.Score2) ||
|
|
175
|
+
(bout.Person2Id === participant.PersonId && bout.Score2 < bout.Score1)).length;
|
|
176
|
+
|
|
177
|
+
const totalHitsScored = completedBouts.reduce((sum, bout) => sum + (bout.Person1Id === participant.PersonId ? bout.Score1 : bout.Score2), 0);
|
|
178
|
+
const totalHitsReceived = completedBouts.reduce((sum, bout) => sum + (bout.Person1Id === participant.PersonId ? bout.Score2 : bout.Score1), 0);
|
|
179
|
+
|
|
180
|
+
const indicator = totalHitsScored - totalHitsReceived;
|
|
181
|
+
const winPercent = completedBouts.length > 0 ? ((wins / completedBouts.length) * 100).toFixed(0) : '0.00';
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
WP: `${winPercent}%`,
|
|
185
|
+
HS: totalHitsScored,
|
|
186
|
+
HR: totalHitsReceived,
|
|
187
|
+
IND: indicator,
|
|
188
|
+
W: wins,
|
|
189
|
+
L: losses,
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
</script>
|
|
195
|
+
|