PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.9
Yatra – Travel Booking & Tour Operator Software v3.0.9
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
yatra / templates / partials / availability-card.php

availability-card.php in Yatra – Travel Booking & Tour Operator Software 3.0.9, at templates/partials/availability-card.php

584 lines 36.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Single departure card for trip availability (included in a loop).
4 * @package Yatra
5 * @var array $card @var int $index @var object $trip_data @var string $pricing_type @var string $selected_date_filter
6 */
7 defined('ABSPATH') || exit;
8 $card = $card ?? [];
9 $index = (int) ($index ?? 0);
10 $trip_data = $trip_data ?? (object) [];
11 $pricing_type = $pricing_type ?? 'regular';
12 $selected_date_filter = $selected_date_filter ?? '';
13 $trip_id = !empty($trip_data->id) ? (int) $trip_data->id : 0;
14 $max_travelers = (int) ($trip_data->max_travelers ?? 20);
15 $item_id = isset($card['id']) ? (string) $card['id'] : (string) $index;
16
17 // Get pricing from card (already calculated with dynamic pricing in Controller)
18 $sale_price = (float) ($card['sale_price'] ?? 0);
19 $original_price = (float) ($card['original_price'] ?? 0);
20
21 $initial_total_price = $sale_price;
22
23 $seats_available = (int) ($card['seats_available'] ?? 0);
24 $is_limited = !empty($card['is_limited']);
25 $card_status = $card['status'] ?? 'available';
26 $is_sold_out = isset($card['is_sold_out']) && $card['is_sold_out'];
27 if ($is_sold_out || $card_status === 'sold_out') {
28 $card_status = 'sold_out';
29 }
30
31 // Match Y-m-d even if DB returns datetime (e.g. 2026-08-13 00:00:00)
32 $yatra_card_date_norm = static function ($v): string {
33 $v = trim((string) $v);
34 return preg_match('/^(\d{4}-\d{2}-\d{2})/', $v, $m) ? $m[1] : $v;
35 };
36 $sel_norm = $yatra_card_date_norm($selected_date_filter ?? '');
37 $card_date_norm = $yatra_card_date_norm($card['data_date'] ?? '');
38 $is_selected_card = ($sel_norm !== '' && $card_date_norm !== '' && $sel_norm === $card_date_norm);
39 $should_be_open = $is_selected_card || $index === 0;
40
41 // Apply Yatra date format to availability header dates when the controller returns ISO.
42 $yatra_format_card_date_for_display = static function ($value): string {
43 $raw = trim((string) $value);
44 if ($raw === '') {
45 return '';
46 }
47 // Only transform ISO-like values; leave already-human strings unchanged.
48 if (!preg_match('/^\d{4}-\d{2}-\d{2}/', $raw)) {
49 return $raw;
50 }
51 return \Yatra\Helpers\FormatHelper::formatDate($raw);
52 };
53
54 $from_date_display = $yatra_format_card_date_for_display($card['from_date'] ?? '');
55 $to_date_display = $yatra_format_card_date_for_display($card['to_date'] ?? '');
56 $date_display_display = $yatra_format_card_date_for_display($card['date_display'] ?? '');
57
58 $dp_disp = $card['dynamic_pricing_display'] ?? [];
59 $dp_show_original = filter_var($dp_disp['show_original_price'] ?? true, FILTER_VALIDATE_BOOLEAN);
60 $dp_show_savings = filter_var($dp_disp['show_savings_badge'] ?? true, FILTER_VALIDATE_BOOLEAN);
61 $dp_show_urgency = filter_var($dp_disp['show_urgency_messages'] ?? false, FILTER_VALIDATE_BOOLEAN);
62 $dp_urgency_messages = [];
63 if (!empty($card['dynamic_pricing_urgency_messages']) && is_array($card['dynamic_pricing_urgency_messages'])) {
64 $dp_urgency_messages = $card['dynamic_pricing_urgency_messages'];
65 }
66
67 $dp_enabled = (bool) apply_filters('yatra_dynamic_pricing_enabled', false);
68
69 // Display amounts for this card (must match traveler row data-price, including per-category DP).
70 $display_sale_price = $sale_price;
71 $display_original_price = $original_price;
72 $card_pricing_type_header = $card['pricing_type'] ?? $pricing_type ?? 'regular';
73 if ($card_pricing_type_header === 'traveler_based' && !empty($card['traveler_pricing']) && is_array($card['traveler_pricing'])) {
74 $first_traveler = $card['traveler_pricing'][0];
75 $first_traveler = is_array($first_traveler) ? (object) $first_traveler : $first_traveler;
76 $pt_price = 0.0;
77 if (isset($first_traveler->effective_price) && (float) $first_traveler->effective_price > 0) {
78 $pt_price = (float) $first_traveler->effective_price;
79 } elseif (isset($first_traveler->discounted_price) && (float) $first_traveler->discounted_price > 0) {
80 $pt_price = (float) $first_traveler->discounted_price;
81 } elseif (isset($first_traveler->original_price) && (float) $first_traveler->original_price > 0) {
82 $pt_price = (float) $first_traveler->original_price;
83 }
84 if ($dp_enabled && $pt_price > 0) {
85 $pt_orig_dp = (float) ($first_traveler->original_price ?? 0);
86 $pt_disc_dp = (float) ($first_traveler->discounted_price ?? $first_traveler->sale_price ?? $first_traveler->effective_price ?? 0);
87 if ($pt_disc_dp <= 0) {
88 $pt_disc_dp = $pt_price;
89 }
90 $pt_price = (float) apply_filters('yatra_availability_price', $pt_price, $trip_id, [
91 'departure_date' => $card['date'] ?? null,
92 'spots_remaining' => $card['spots_remaining'] ?? null,
93 'availability_id' => $item_id,
94 'price_type_id' => $first_traveler->id ?? ($first_traveler->price_type_id ?? null),
95 'original_price' => $pt_orig_dp > 0 ? $pt_orig_dp : $pt_price,
96 'discounted_price' => $pt_disc_dp > 0 ? $pt_disc_dp : $pt_price,
97 ]);
98 }
99 if ($pt_price > 0) {
100 $display_sale_price = $pt_price;
101 }
102 $fo = (float) ($first_traveler->original_price ?? 0);
103 if ($fo > 0) {
104 $display_original_price = $fo;
105 }
106 }
107 $yatra_av_badges_row_original = $dp_show_original && $display_original_price > $display_sale_price && $display_original_price > 0;
108 $yatra_av_badges_row_savings = $dp_show_savings && !empty($card['discount_text']);
109 $yatra_av_discount_is_surge = $yatra_av_badges_row_savings && strpos((string) $card['discount_text'], '+') === 0;
110 $initial_total_price = $display_sale_price;
111 ?>
112 <div class="yatra-availability-card <?php echo $should_be_open ? 'open' : ''; ?> <?php echo $is_selected_card ? 'selected' : ''; ?> <?php echo $is_limited ? 'limited' : ''; ?> yatra-card-status-<?php echo esc_attr($card_status); ?>"
113 data-availability-id="<?php echo esc_attr($item_id); ?>"
114 data-month="<?php echo esc_attr($card['data_month'] ?? ''); ?>"
115 data-date="<?php echo esc_attr($card['data_date'] ?? ''); ?>"
116 data-price="<?php echo esc_attr($display_sale_price); ?>"
117 data-seats="<?php echo esc_attr($seats_available); ?>"
118 data-item="<?php echo esc_attr($item_id); ?>"
119 data-index="<?php echo esc_attr($index); ?>">
120
121 <!-- Card Header (Clickable) -->
122 <div class="yatra-availability-card-header yatra-availability-toggle" role="button" tabindex="0" aria-label="<?php esc_attr_e('Toggle availability details', 'yatra'); ?>">
123
124 <?php if (!empty($card['date_display'])): ?>
125 <!-- Day Trip: Show date as header -->
126 <div class="yatra-card-date-header">
127 <?php echo yatra_svg_icon('calendar', 'yatra-icon-sm'); ?>
128 <?php echo esc_html($date_display_display); ?>
129 </div>
130 <?php endif; ?>
131
132 <!-- Badge row: own line below day-trip date (when present), above the main grid — avoids overlap with price/seats -->
133 <div class="yatra-card-badges-container">
134 <?php if ($yatra_av_badges_row_original) : ?>
135 <span class="yatra-availability-badge-row__original" title="<?php esc_attr_e('Original price', 'yatra'); ?>"><?php echo yatra_format_price($display_original_price); ?></span>
136 <?php endif; ?>
137 <?php if ($yatra_av_badges_row_savings) : ?>
138 <span class="yatra-card-discount-badge <?php echo $yatra_av_discount_is_surge ? 'yatra-badge-higher' : 'yatra-badge-discount'; ?>"><?php echo esc_html((string) $card['discount_text']); ?></span>
139 <?php endif; ?>
140 <?php if ($dp_show_urgency && !empty($dp_urgency_messages)) : ?>
141 <?php foreach ($dp_urgency_messages as $dp_urgency_line) : ?>
142 <span class="yatra-availability-urgency-message"><?php echo esc_html((string) $dp_urgency_line); ?></span>
143 <?php endforeach; ?>
144 <?php endif; ?>
145 <?php
146 $card_status = $card['status'] ?? 'available';
147 $is_sold_out = isset($card['is_sold_out']) && $card['is_sold_out'];
148
149 if ($is_sold_out || $card_status === 'sold_out') {
150 echo '<div class="yatra-badge-status yatra-badge-sold-out">' . esc_html__('Sold Out', 'yatra') . '</div>';
151 } elseif ($card_status === 'limited') {
152 echo '<div class="yatra-badge-status yatra-badge-limited">' . esc_html__('Limited', 'yatra') . '</div>';
153 } elseif ($card_status === 'blocked') {
154 echo '<div class="yatra-badge-status yatra-badge-blocked">' . esc_html__('Blocked', 'yatra') . '</div>';
155 } elseif ($card_status === 'closed') {
156 echo '<div class="yatra-badge-status yatra-badge-closed">' . esc_html__('Closed', 'yatra') . '</div>';
157 } elseif ($card_status === 'cancelled') {
158 echo '<div class="yatra-badge-status yatra-badge-cancelled">' . esc_html__('Cancelled', 'yatra') . '</div>';
159 } else {
160 echo '<div class="yatra-badge-status yatra-badge-available">' . esc_html__('Available', 'yatra') . '</div>';
161 }
162 ?>
163 </div>
164
165 <div class="yatra-card-header-grid <?php echo !empty($card['date_display']) ? 'yatra-day-trip-grid' : ''; ?>">
166
167 <!-- Start/Departure -->
168 <div class="yatra-card-header-item">
169 <div class="yatra-card-header-label"><?php echo esc_html($card['from_label'] ?? __('Departure', 'yatra')); ?></div>
170 <div class="yatra-card-header-date"><?php echo esc_html($from_date_display); ?></div>
171 <div class="yatra-card-header-location"><?php echo esc_html($card['from_location'] ?? ''); ?></div>
172 </div>
173
174 <!-- End/Return -->
175 <div class="yatra-card-header-item">
176 <div class="yatra-card-header-label"><?php echo esc_html($card['to_label'] ?? __('Return', 'yatra')); ?></div>
177 <div class="yatra-card-header-date"><?php echo esc_html($to_date_display); ?></div>
178 <div class="yatra-card-header-location"><?php echo esc_html($card['to_location'] ?? ''); ?></div>
179 </div>
180
181 <!-- Seats -->
182 <div class="yatra-card-header-item">
183 <div class="yatra-card-header-label"><?php esc_html_e('Seats remaining', 'yatra'); ?></div>
184 <div class="yatra-card-header-seats <?php echo $is_limited ? 'limited' : ''; ?>"><?php echo esc_html($card['seats'] ?? '0'); ?></div>
185 <div class="yatra-card-header-sub"><?php esc_html_e('per departure', 'yatra'); ?></div>
186 </div>
187
188 <!-- Price (sale only; struck original + % badge sit in .yatra-card-badges-container with status) -->
189 <div class="yatra-card-header-price">
190 <div class="yatra-card-price-group yatra-card-price-group--stack">
191 <?php
192 echo '<span class="yatra-sale-price">' . yatra_format_price($display_sale_price) . '</span>';
193 ?>
194 </div>
195 <span class="yatra-price-label"><?php echo esc_html__('per person', 'yatra'); ?></span>
196 </div>
197
198 <!-- Toggle Arrow -->
199 <div class="yatra-card-header-arrow">
200 <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
201 <path d="M5 7.5L10 12.5L15 7.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
202 </svg>
203 </div>
204 </div>
205 </div>
206
207 <!-- Card Body (Expandable) -->
208 <div class="yatra-availability-card-body">
209 <div class="yatra-card-body-content">
210 <div class="yatra-card-info-grid">
211 <div class="yatra-card-info-item">
212 <div class="yatra-card-info-icon">
213 <?php echo yatra_svg_icon('calendar', 'yatra-icon-sm'); ?>
214 </div>
215 <div class="yatra-card-info-content">
216 <div class="yatra-card-info-label"><?php esc_html_e('Duration', 'yatra'); ?></div>
217 <div class="yatra-card-info-value">
218 <?php
219 // Prefer the per-card span (matches this card's departure/return
220 // dates); fall back to the trip default for sample cards.
221 $duration_days = (int) ($card['duration_days'] ?? $trip_data->duration_days ?? 1);
222 echo esc_html($duration_days . ' ' . _n('Day', 'Days', $duration_days, 'yatra'));
223 ?>
224 </div>
225 </div>
226 </div>
227
228 <div class="yatra-card-info-item">
229 <div class="yatra-card-info-icon">
230 <?php echo yatra_svg_icon('users', 'yatra-icon-sm'); ?>
231 </div>
232 <div class="yatra-card-info-content">
233 <div class="yatra-card-info-label"><?php esc_html_e('Seats left', 'yatra'); ?></div>
234 <div class="yatra-card-info-value"><?php
235 /* translators: %d: number of seats remaining. */
236 echo esc_html(sprintf(_n('%d seat', '%d seats', $seats_available, 'yatra'), $seats_available));
237 ?></div>
238 </div>
239 </div>
240
241 <?php
242 $cancellation_policy = '';
243 if (method_exists($trip_data, 'getCancellationPolicy')) {
244 $cancellation_policy = $trip_data->getCancellationPolicy();
245 } elseif (isset($trip_data->cancellation_policy)) {
246 $cancellation_policy = $trip_data->cancellation_policy;
247 }
248
249 $cancellation_label = '';
250 $cancellation_value = '';
251 if (!empty($cancellation_policy)) {
252 // Extract cancellation info from policy
253 $policy_lower = strtolower($cancellation_policy);
254 if (strpos($policy_lower, 'free') !== false || strpos($policy_lower, 'no charge') !== false) {
255 $cancellation_label = __('Free Cancellation', 'yatra');
256 if (preg_match('/(\d+)\s*(hour|day|week)s?\s*before/i', $cancellation_policy, $matches)) {
257 $time_value = $matches[1];
258 $time_unit = $matches[2];
259 $cancellation_value = sprintf(
260 /* translators: 1: numeric amount, 2: time unit (hour/day/week), 3: plural "s" suffix if applicable. */
261 __('Up to %1$d %2$s%3$s before', 'yatra'),
262 $time_value,
263 $time_unit,
264 $time_value > 1 ? 's' : ''
265 );
266 } else {
267 $cancellation_value = __('No charge', 'yatra');
268 }
269 } else {
270 // Non-free cancellation
271 $cancellation_label = __('Cancellation Policy', 'yatra');
272 $cancellation_value = __('See details', 'yatra');
273 }
274 } else {
275 // Default fallback when no policy is set
276 $cancellation_label = __('Cancellation Policy', 'yatra');
277 $cancellation_value = __('May apply', 'yatra');
278 }
279 ?>
280 <div class="yatra-card-info-item">
281 <div class="yatra-card-info-icon">
282 <?php echo yatra_svg_icon('check', 'yatra-icon-sm'); ?>
283 </div>
284 <div class="yatra-card-info-content">
285 <div class="yatra-card-info-label"><?php echo esc_html($cancellation_label); ?></div>
286 <div class="yatra-card-info-value"><?php echo esc_html($cancellation_value); ?></div>
287 </div>
288 </div>
289 </div>
290
291 <div class="yatra-card-traveler-section">
292 <label class="yatra-card-traveler-label"><?php esc_html_e('Travelers', 'yatra'); ?></label>
293
294 <?php
295 // Use card-level pricing type as source of truth
296 $card_pricing_type = $card['pricing_type'] ?? $pricing_type ?? 'regular';
297 $card_price_types = [];
298
299 // Use availability-specific traveler_pricing for traveler-based pricing
300 if ($card_pricing_type === 'traveler_based' && !empty($card['traveler_pricing'])) {
301 $card_price_types = $card['traveler_pricing'];
302 }
303
304 // Normalize traveler pricing to objects if array
305 $normalized_price_types = [];
306 foreach ($card_price_types as $cpt) {
307 if (is_array($cpt)) {
308 $normalized_price_types[] = (object) $cpt;
309 } else {
310 $normalized_price_types[] = $cpt;
311 }
312 }
313 ?>
314
315 <?php if ($card_pricing_type === 'traveler_based' && !empty($normalized_price_types)): ?>
316 <!-- Traveler-based pricing: Show dynamic categories -->
317 <?php
318 // Build traveler rows directly from card-level traveler_pricing data
319 $traveler_rows = [];
320 $display_parts = [];
321 foreach ($normalized_price_types as $pt_index => $pt) {
322 $pt_label = $pt->category_label ?? $pt->label ?? __('Traveler', 'yatra');
323 $pt_pricing_mode = $pt->pricing_mode ?? 'per_person';
324 $pt_is_per_group = ($pt_pricing_mode === 'per_group');
325 $pt_category_id = $pt->category_id ?? $pt_index;
326 $pt_default = $pt_index === 0 ? 1 : 0;
327 $pt_min_qty = 0;
328 $pt_max_qty = (int) min($seats_available, $max_travelers);
329 // Cap a per-group "block" category at its max group
330 // size (still bounded by seats). "per_block" mode may
331 // exceed it (additional group blocks), so skip.
332 if ($pt_is_per_group && !empty($pt->max_pax)
333 && (($pt->group_overflow ?? 'block') !== 'per_block')) {
334 $pt_max_qty = (int) min($pt_max_qty, (int) $pt->max_pax);
335 }
336
337 // Determine price with fallback chain
338 $pt_price = 0;
339 if (isset($pt->effective_price) && $pt->effective_price > 0) {
340 $pt_price = (float) $pt->effective_price;
341 } elseif (isset($pt->discounted_price) && $pt->discounted_price > 0) {
342 $pt_price = (float) $pt->discounted_price;
343 } elseif (isset($pt->original_price) && $pt->original_price > 0) {
344 $pt_price = (float) $pt->original_price;
345 }
346
347 // Apply dynamic pricing to traveler category prices
348 if ($dp_enabled && $pt_price > 0) {
349 $pt_orig_dp = (float) ($pt->original_price ?? 0);
350 $pt_disc_dp = (float) ($pt->discounted_price ?? $pt->sale_price ?? $pt->effective_price ?? 0);
351 if ($pt_disc_dp <= 0) {
352 $pt_disc_dp = $pt_price;
353 }
354 $pt_price = apply_filters('yatra_availability_price', $pt_price, $trip_id, [
355 'departure_date' => $card['date'] ?? null,
356 'spots_remaining' => $card['spots_remaining'] ?? null,
357 'availability_id' => $item_id,
358 'price_type_id' => $pt->id ?? ($pt->price_type_id ?? null),
359 'original_price' => $pt_orig_dp > 0 ? $pt_orig_dp : $pt_price,
360 'discounted_price' => $pt_disc_dp > 0 ? $pt_disc_dp : $pt_price,
361 ]);
362 }
363
364 // Age info
365 $pt_age_text = '';
366 $pt_age_min = isset($pt->age_min) ? (int) $pt->age_min : null;
367 $pt_age_max = isset($pt->age_max) ? (int) $pt->age_max : null;
368 if ($pt_age_min !== null || $pt_age_max !== null) {
369 if ($pt_age_min !== null && $pt_age_max !== null) {
370 /* translators: 1: minimum age, 2: maximum age. */
371 $pt_age_text = sprintf(__('(Age %1$d-%2$d)', 'yatra'), $pt_age_min, $pt_age_max);
372 } elseif ($pt_age_min !== null) {
373 /* translators: %d: minimum age. */
374 $pt_age_text = sprintf(__('(Age %d+)', 'yatra'), $pt_age_min);
375 } else {
376 /* translators: %d: maximum age. */
377 $pt_age_text = sprintf(__('(Up to age %d)', 'yatra'), $pt_age_max);
378 }
379 }
380
381 // Build pricing label for per_group mode
382 $pricing_label = '';
383 if ($pt_is_per_group) {
384 if (!empty($pt->min_pax) && !empty($pt->max_pax)) {
385 /* translators: 1: minimum pax for the group price, 2: maximum pax. */
386 $pricing_label = sprintf(__('per group (%1$d-%2$d pax)', 'yatra'), $pt->min_pax, $pt->max_pax);
387 } elseif (!empty($pt->max_pax)) {
388 /* translators: %d: maximum pax for the group price. */
389 $pricing_label = sprintf(__('per group (up to %d pax)', 'yatra'), $pt->max_pax);
390 } elseif (!empty($pt->min_pax)) {
391 /* translators: %d: minimum pax for the group price. */
392 $pricing_label = sprintf(__('per group (%d+ pax)', 'yatra'), $pt->min_pax);
393 } else {
394 $pricing_label = __('per group', 'yatra');
395 }
396 }
397
398 // Build price HTML
399 $pt_price_html = '';
400 if ($pt_price > 0) {
401 $pt_price_html = '<div class="yatra-quantity-price-wrapper">';
402 $pt_price_html .= '<span class="yatra-quantity-price">' . yatra_format_price($pt_price) . '</span>';
403 if ($pt_is_per_group) {
404 $pt_price_html .= '<span class="yatra-pricing-mode-label yatra-pricing-mode-group">' . esc_html($pricing_label) . '</span>';
405 }
406 $pt_price_html .= '</div>';
407 }
408
409 $input_id = 'traveler_' . $pt_category_id;
410
411 $traveler_rows[] = [
412 'label' => $pt_label,
413 'subtitle' => $pt_age_text,
414 'price_html' => $pt_price_html,
415 'row_attrs' => [
416 'data-category-id' => $pt_category_id,
417 'data-price' => $pt_price,
418 'data-pricing-mode' => $pt_pricing_mode,
419 'data-group-overflow' => $pt->group_overflow ?? 'block',
420 'data-max-pax' => $pt->max_pax ?? '',
421 ],
422 'minus_disabled' => $pt_default <= $pt_min_qty,
423 'plus_disabled' => $pt_default >= $pt_max_qty,
424 'minus_attrs' => [
425 'data-target' => $input_id,
426 'data-item' => $item_id,
427 'data-category' => $pt_category_id,
428 /* translators: %s: traveler category label (e.g. "Adult", "Child"). */
429 'aria-label' => sprintf(__('Decrease %s', 'yatra'), $pt_label),
430 ],
431 'plus_attrs' => [
432 'data-target' => $input_id,
433 'data-item' => $item_id,
434 'data-category' => $pt_category_id,
435 /* translators: %s: traveler category label (e.g. "Adult", "Child"). */
436 'aria-label' => sprintf(__('Increase %s', 'yatra'), $pt_label),
437 ],
438 'input_attrs' => [
439 'id' => $input_id,
440 'name' => 'travelers[' . $pt_category_id . ']',
441 'value' => $pt_default,
442 'min' => $pt_min_qty,
443 'max' => $pt_max_qty,
444 'data-item' => $item_id,
445 'data-category' => $pt_category_id,
446 'data-category-label' => $pt_label,
447 'data-price' => $pt_price,
448 'data-pricing-mode' => $pt_pricing_mode,
449 'data-group-overflow' => $pt->group_overflow ?? 'block',
450 'data-max-pax' => $pt->max_pax ?? '',
451 ],
452 ];
453
454 if ($pt_default > 0) {
455 $display_parts[] = $pt_label . ' x ' . $pt_default;
456 }
457 }
458
459 $traveler_display_text = !empty($display_parts) ? implode(', ', $display_parts) : __('Select travelers', 'yatra');
460
461 // Setup variables for traveler-selector.php using availability-specific data
462 $root_id = '';
463 $root_class = 'yatra-booking-field-select yatra-participants-select yatra-availability-participants';
464 $container_attrs = [
465 'data-item' => $item_id,
466 ];
467
468 $display_id = 'participants-display';
469 $display_class = 'yatra-participants-display yatra-availability-participants-display';
470 $display_attrs = [
471 'data-item' => $item_id,
472 ];
473
474 $dropdown_id = '';
475 $dropdown_class = 'yatra-booking-quantity-selector yatra-availability-quantity-selector';
476 $dropdown_attrs = [
477 'data-item' => $item_id,
478 ];
479
480 $display_text = $traveler_display_text;
481 $icon_html = yatra_svg_icon('users', 'yatra-icon-sm');
482 $rows = $traveler_rows;
483
484 include YATRA_PLUGIN_PATH . 'templates/partials/traveler-selector.php';
485 ?>
486 <?php else: ?>
487 <!-- Regular pricing: Show simple number of travelers -->
488 <div class="yatra-booking-travelers-simple yatra-availability-travelers-simple" data-item="<?php echo esc_attr($item_id); ?>">
489 <div class="yatra-booking-field-icon">
490 <?php echo yatra_svg_icon('users', 'yatra-icon-sm'); ?>
491 </div>
492 <div class="yatra-quantity-controls-inline">
493 <button type="button" class="yatra-quantity-btn yatra-quantity-minus" data-target="num-travelers-<?php echo esc_attr($item_id); ?>" aria-label="<?php esc_attr_e('Decrease travelers', 'yatra'); ?>" disabled>
494 <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
495 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4"/>
496 </svg>
497 </button>
498 <input type="number"
499 id="num-travelers-<?php echo esc_attr($item_id); ?>"
500 class="yatra-quantity-input-simple yatra-availability-num-travelers"
501 data-item="<?php echo esc_attr($item_id); ?>"
502 value="1"
503 min="1"
504 max="<?php echo esc_attr(min($seats_available, $max_travelers)); ?>"
505 readonly
506 data-price="<?php echo esc_attr($display_sale_price); ?>">
507 <button type="button" class="yatra-quantity-btn yatra-quantity-plus" data-target="num-travelers-<?php echo esc_attr($item_id); ?>" aria-label="<?php esc_attr_e('Increase travelers', 'yatra'); ?>">
508 <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
509 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
510 </svg>
511 </button>
512 </div>
513 <span class="yatra-travelers-label"><?php esc_html_e('travelers', 'yatra'); ?></span>
514 </div>
515 <?php endif; ?>
516
517 <div class="yatra-card-action-help"><?php esc_html_e('Select travelers to update the total, then click Book Now.', 'yatra'); ?></div>
518 </div>
519 </div>
520
521 <!-- Booking Row at Bottom -->
522 <div class="yatra-card-booking-row">
523 <div class="yatra-card-total-box">
524 <div class="yatra-card-total-label"><?php esc_html_e('Total', 'yatra'); ?></div>
525 <div class="yatra-card-total-note" data-item="<?php echo esc_attr($item_id); ?>"><?php esc_html_e('for 1 traveler', 'yatra'); ?></div>
526 <div class="yatra-card-total-amount" data-item="<?php echo esc_attr($item_id); ?>" data-base-price="<?php echo esc_attr($initial_total_price); ?>">
527 <?php
528 $formatted_total = yatra_format_price($initial_total_price);
529 echo esc_html($formatted_total);
530 ?>
531 </div>
532 </div>
533 <?php
534 // Prepare price_types JSON for JavaScript
535 $price_types_json = '';
536 if (!empty($normalized_price_types)) {
537 $price_types_for_js = array_map(function($pt) {
538 $pt = (object) $pt;
539 return [
540 'category_id' => $pt->category_id ?? null,
541 'category_label' => $pt->category_label ?? $pt->label ?? '',
542 'original_price' => $pt->original_price ?? 0,
543 'sale_price' => $pt->sale_price ?? null,
544 'discounted_price' => $pt->discounted_price ?? null,
545 'effective_price' => $pt->effective_price ?? $pt->sale_price ?? $pt->discounted_price ?? $pt->original_price ?? 0,
546 'age_min' => $pt->age_min ?? null,
547 'age_max' => $pt->age_max ?? null,
548 ];
549 }, $normalized_price_types);
550 $price_types_json = wp_json_encode($price_types_for_js);
551 }
552
553 // Get departure time - prefer card data, fallback to from_date if it looks like time
554 $card_departure_time = '';
555 if (!empty($card['departure_time'])) {
556 $card_departure_time = $card['departure_time'];
557 } elseif (!empty($card['from_date']) && preg_match('/^\d{1,2}:\d{2}\s*(AM|PM)?$/i', $card['from_date'])) {
558 $card_departure_time = $card['from_date'];
559 }
560
561 // Is this a day trip?
562 $is_card_day_trip = !empty($card['is_day_trip']) || !empty($card['date_display']);
563 ?>
564 <button type="button"
565 class="yatra-card-book-btn"
566 data-trip-id="<?php echo esc_attr($trip_id); ?>"
567 data-availability-id="<?php echo esc_attr($item_id); ?>"
568 data-date="<?php echo esc_attr($card['data_date'] ?? ''); ?>"
569 data-departure-time="<?php echo esc_attr($card_departure_time); ?>"
570 data-is-day-trip="<?php echo $is_card_day_trip ? '1' : '0'; ?>"
571 data-pricing-type="<?php echo esc_attr($card_pricing_type); ?>"
572 data-price-types="<?php echo esc_attr($price_types_json); ?>"
573 data-price="<?php echo esc_attr($display_sale_price); ?>"
574 data-item="<?php echo esc_attr($item_id); ?>">
575 <?php echo yatra_svg_icon('shopping-cart', 'yatra-icon-sm'); ?>
576 <span class="yatra-card-book-btn-text">
577 <span class="yatra-card-book-btn-title"><?php esc_html_e('Book Now', 'yatra'); ?></span>
578 <span class="yatra-card-book-btn-subtitle"><?php esc_html_e('Continue to booking', 'yatra'); ?></span>
579 </span>
580 </button>
581 </div>
582 </div>
583 </div>
584