PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / trunk
Booking for Appointments and Events Calendar – Amelia vtrunk
2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / v3 / src / views / public / EventForm / Common / Parts / EventTicket.vue
ameliabooking / v3 / src / views / public / EventForm / Common / Parts Last commit date
BringingAnyone.vue 5 days ago DescriptionItem.vue 5 days ago EmptyState.vue 5 days ago EventCard.vue 5 days ago EventEmployee.vue 5 days ago EventTicket.vue 5 days ago GalleryCarousel.vue 5 days ago
EventTicket.vue
360 lines
1 <template>
2 <div
3 class="am-ct"
4 :class="[{ 'am-readonly': props.readonly }, responsiveClass]"
5 :style="cssVars"
6 role="group"
7 :aria-labelledby="ticketHeadingId"
8 >
9 <div class="am-ct__info">
10 <p :id="ticketHeadingId" class="am-ct__info-name">
11 {{ props.ticket.name }}
12 </p>
13 <p
14 :id="ticketStatusId"
15 class="am-ct__info-spots"
16 role="status"
17 aria-live="polite"
18 aria-atomic="true"
19 >
20 <span v-if="componentWidth <= 500" class="am-ct__info-spots__price">
21 {{ useFormattedPrice(props.ticket.price) }}
22 </span>
23 <span v-if="!props.capacity" class="am-ct__info-spots__number">
24 {{
25 isWaitingList
26 ? ticketCalculation() > 0
27 ? ticketCalculation()
28 : ''
29 : ticketCalculation()
30 }}
31 </span>
32 <span v-if="!props.capacity" class="am-ct__info-spots__text">
33 {{ ticketText() }}
34 </span>
35 </p>
36 </div>
37 <div class="am-ct__action" :class="[{ 'am-readonly': props.readonly }, responsiveClass]">
38 <p v-if="componentWidth > 500" class="am-ct__action-price">
39 {{ useFormattedPrice(props.ticket.price) }}
40 </p>
41 <AmInputNumber
42 v-if="!props.readonly"
43 :id="ticketInputId"
44 v-model="spots"
45 size="small"
46 :min="0"
47 :max="selectedEvent.bringingAnyone ? spotsLimitation : 1"
48 :name="`event-ticket-${props.ticket.id}`"
49 :aria-label="ticketInputAriaLabel"
50 :aria-describedby="ticketStatusId"
51 :disabled="disableSelection"
52 @change="updateSpots"
53 />
54 </div>
55 </div>
56 </template>
57
58 <script setup>
59 import AmInputNumber from '../../../../_components/input-number/AmInputNumber.vue'
60
61 // * Import from Vue
62 import { computed, inject } from 'vue'
63
64 // * Import form Vuex
65 import { useStore } from 'vuex'
66
67 // * Composables
68 import { useFormattedPrice } from '../../../../../assets/js/common/formatting'
69 import { useColorTransparency } from '../../../../../assets/js/common/colorManipulation'
70 import { useResponsiveClass } from '../../../../../assets/js/common/responsive'
71
72 // * Define store
73 const store = useStore()
74 /**
75 * Component Props
76 */
77 const props = defineProps({
78 customizedLabels: {
79 type: Object,
80 required: true,
81 },
82 ticket: {
83 type: Object,
84 },
85 capacity: {
86 type: Number,
87 },
88 extraPeople: {
89 type: Number,
90 },
91 readonly: {
92 type: Boolean,
93 default: false,
94 },
95 inDialog: {
96 type: Boolean,
97 default: false,
98 },
99 })
100
101 // * Responsive - Container Width
102 let cWidth = inject('containerWidth')
103 let dWidth = inject('dialogWidth')
104
105 let componentWidth = computed(() => {
106 return props.inDialog ? dWidth.value : cWidth.value
107 })
108
109 let responsiveClass = computed(() => useResponsiveClass(componentWidth.value))
110
111 let selectedEvent = computed(() =>
112 store.getters['eventEntities/getEvent'](store.getters['eventBooking/getSelectedEventId']),
113 )
114
115 const ticketName = computed(() => props.ticket?.name || '')
116 const ticketHeadingId = computed(() => `am-ct-name-${props.ticket?.id || 'default'}`)
117 const ticketStatusId = computed(() => `am-ct-status-${props.ticket?.id || 'default'}`)
118 const ticketInputId = computed(() => `am-ct-input-${props.ticket?.id || 'default'}`)
119 const ticketInputAriaLabel = computed(() => {
120 return `${props.customizedLabels.event_ticket_types || 'Ticket type'}: ${ticketName.value}`
121 })
122
123 let spots = computed({
124 get: () => store.getters['tickets/getTicketNumber'](props.ticket.id),
125 set: (val) => {
126 let obj = {
127 id: props.ticket.id,
128 numb: val ? val : 0,
129 }
130 store.commit('tickets/setTicketNumber', obj)
131 },
132 })
133
134 let spotsLimitation = computed(() => {
135 if (props.extraPeople !== null && props.extraPeople !== undefined) {
136 if (props.capacity !== null && props.capacity !== undefined) {
137 const selectedWithoutCurrent = store.getters['tickets/getEventGlobalSpots'] - spots.value
138 const capacityLimit = props.capacity - selectedWithoutCurrent
139 const extraPeopleLimit = props.extraPeople + 1 - selectedWithoutCurrent
140 return Math.max(0, Math.min(capacityLimit, extraPeopleLimit))
141 }
142
143 let limitCorrection =
144 props.extraPeople + 1 - (store.getters['tickets/getEventGlobalSpots'] - spots.value)
145 if (isWaitingList.value) {
146 return Math.max(
147 0,
148 limitCorrection >= props.ticket.waitingListSpots - props.ticket.waiting
149 ? props.ticket.waitingListSpots - props.ticket.waiting
150 : limitCorrection,
151 )
152 }
153 return Math.max(
154 0,
155 limitCorrection >= props.ticket.spots - props.ticket.sold
156 ? props.ticket.spots - props.ticket.sold
157 : limitCorrection,
158 )
159 }
160
161 if (props.capacity !== null && props.capacity !== undefined)
162 return Math.max(
163 0,
164 props.capacity - (store.getters['tickets/getEventGlobalSpots'] - spots.value),
165 )
166
167 return Math.max(
168 0,
169 isWaitingList.value
170 ? props.ticket.waitingListSpots - props.ticket.waiting
171 : props.ticket.spots - props.ticket.sold,
172 )
173 })
174
175 let disableSelection = computed(() => {
176 if (selectedEvent.value.bringingAnyone) {
177 return spotsLimitation.value <= 0
178 }
179
180 if (!selectedEvent.value.maxCustomCapacity) {
181 return (
182 store.getters['tickets/getEventGlobalSpots'] !== spots.value ||
183 (!isWaitingList.value && props.ticket.spots === props.ticket.sold) ||
184 (isWaitingList.value && props.ticket.waiting === props.ticket.waitingListSpots)
185 )
186 }
187
188 return store.getters['tickets/getEventGlobalSpots'] !== spots.value
189 })
190
191 function updateSpots(newValue, oldValue) {
192 if (
193 (props.capacity !== null && props.capacity !== undefined) ||
194 (props.extraPeople !== null && props.extraPeople !== undefined) ||
195 !selectedEvent.value.bringingAnyone
196 ) {
197 store.commit('tickets/setEventGlobalSpots', newValue - oldValue)
198 }
199 }
200
201 let isWaitingList = computed(() => store.getters['eventWaitingListOptions/getAvailability'])
202
203 function ticketCalculation() {
204 if (isWaitingList.value) {
205 return props.ticket.waiting + spots.value
206 }
207
208 // * No attendees, but there are customers on the waiting list, return 0
209 return selectedEvent.value.full ? 0 : props.ticket.spots - props.ticket.sold - spots.value
210 }
211
212 function ticketText() {
213 if (isWaitingList.value) {
214 if (ticketCalculation() === 0) {
215 return ` ${props.customizedLabels.join_waiting_list}`
216 } else if (ticketCalculation() === 1) {
217 return ` ${props.customizedLabels.person_waiting}`
218 } else {
219 return ` ${props.customizedLabels.people_waiting}`
220 }
221 }
222
223 return ticketCalculation() === 1
224 ? ` ${props.customizedLabels.event_ticket_left}`
225 : ` ${props.customizedLabels.event_tickets_left}`
226 }
227
228 // * Fonts
229 let amFonts = inject('amFonts')
230
231 // * Colors
232 let amColors = inject('amColors')
233
234 // * Css Vars
235 let cssVars = computed(() => {
236 return {
237 '--am-c-font-family': amFonts.value.fontFamily,
238 '--am-c-ct-primary': amColors.value.colorPrimary,
239 '--am-c-ct-bgr': amColors.value.colorMainBgr,
240 '--am-c-ct-text': amColors.value.colorMainText,
241 '--am-c-ct-text-op90': useColorTransparency(amColors.value.colorMainText, 0.9),
242 '--am-c-ct-text-op80': useColorTransparency(amColors.value.colorMainText, 0.8),
243 '--am-c-ct-text-op70': useColorTransparency(amColors.value.colorMainText, 0.7),
244 '--am-c-ct-text-op60': useColorTransparency(amColors.value.colorMainText, 0.6),
245 '--am-c-ct-text-op20': useColorTransparency(amColors.value.colorMainText, 0.2),
246 }
247 })
248 </script>
249
250 <script>
251 export default {
252 name: 'EventTicket',
253 }
254 </script>
255
256 <style lang="scss">
257 @mixin card-ticket {
258 // am - amelia
259 // ct - card ticket
260 .am-ct {
261 width: 100%;
262 display: flex;
263 justify-content: space-between;
264 border: 1px solid var(--am-c-ct-text-op20);
265 border-radius: 8px;
266 padding: 12px;
267 margin-bottom: 12px;
268
269 &.am-rw-420 {
270 flex-direction: column;
271 }
272
273 &__info {
274 display: flex;
275 flex-direction: column;
276 justify-content: space-between;
277
278 &-name {
279 font-weight: 700;
280 font-size: 15px;
281 line-height: 1.6;
282 color: var(--am-c-ct-text); // $shade-900
283 margin: 0;
284 }
285
286 &-spots {
287 font-weight: 400;
288 font-size: 15px;
289 line-height: 1.6;
290 color: var(--am-c-ct-text-op90); // $shade-800
291 margin: 0;
292
293 &__price {
294 font-weight: 700;
295 font-size: 14px;
296 line-height: 20px;
297 color: var(--am-c-ct-primary); // $blue-900
298 margin-right: 8px;
299 }
300
301 &__number {
302 font-weight: 700;
303 }
304 }
305 }
306
307 &__action {
308 display: flex;
309 flex-direction: row;
310 align-items: center;
311 flex: 0 0 auto;
312
313 &-price {
314 font-weight: 700;
315 font-size: 14px;
316 line-height: 20px;
317 color: var(--am-c-ct-primary); // $blue-900
318 }
319
320 .am-input-number {
321 max-width: 130px;
322 margin-left: 8px;
323 }
324
325 &.am-readonly {
326 align-items: flex-start;
327 }
328
329 &.am-rw-420 {
330 width: 100%;
331 margin-top: 8px;
332
333 .am-input-number {
334 max-width: unset;
335 width: 100%;
336 margin-left: 0;
337 }
338 }
339 }
340
341 /* if it's event info step */
342 &.am-readonly {
343 border: none;
344 border-bottom: 1px solid var(--am-c-ct-text-op20);
345 border-radius: 0;
346 }
347 }
348 }
349
350 // Public
351 .amelia-v2-booking #amelia-container {
352 @include card-ticket;
353 }
354
355 // Admin
356 #amelia-app-backend-new {
357 @include card-ticket;
358 }
359 </style>
360