PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.5
Yatra – Travel Booking & Tour Operator Software v3.0.5
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.5, at templates/partials/availability-card.php

571 lines 35.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 $duration_days = (int) ($trip_data->duration_days ?? 1);
220 echo esc_html($duration_days . ' ' . _n('Day', 'Days', $duration_days, 'yatra'));
221 ?>
222 </div>
223 </div>
224 </div>
225
226 <div class="yatra-card-info-item">
227 <div class="yatra-card-info-icon">
228 <?php echo yatra_svg_icon('users', 'yatra-icon-sm'); ?>
229 </div>
230 <div class="yatra-card-info-content">
231 <div class="yatra-card-info-label"><?php esc_html_e('Seats left', 'yatra'); ?></div>
232 <div class="yatra-card-info-value"><?php
233 /* translators: %d: number of seats remaining. */
234 echo esc_html(sprintf(_n('%d seat', '%d seats', $seats_available, 'yatra'), $seats_available));
235 ?></div>
236 </div>
237 </div>
238
239 <?php
240 $cancellation_policy = '';
241 if (method_exists($trip_data, 'getCancellationPolicy')) {
242 $cancellation_policy = $trip_data->getCancellationPolicy();
243 } elseif (isset($trip_data->cancellation_policy)) {
244 $cancellation_policy = $trip_data->cancellation_policy;
245 }
246
247 $cancellation_label = '';
248 $cancellation_value = '';
249 if (!empty($cancellation_policy)) {
250 // Extract cancellation info from policy
251 $policy_lower = strtolower($cancellation_policy);
252 if (strpos($policy_lower, 'free') !== false || strpos($policy_lower, 'no charge') !== false) {
253 $cancellation_label = __('Free Cancellation', 'yatra');
254 if (preg_match('/(\d+)\s*(hour|day|week)s?\s*before/i', $cancellation_policy, $matches)) {
255 $time_value = $matches[1];
256 $time_unit = $matches[2];
257 $cancellation_value = sprintf(
258 /* translators: 1: numeric amount, 2: time unit (hour/day/week), 3: plural "s" suffix if applicable. */
259 __('Up to %1$d %2$s%3$s before', 'yatra'),
260 $time_value,
261 $time_unit,
262 $time_value > 1 ? 's' : ''
263 );
264 } else {
265 $cancellation_value = __('No charge', 'yatra');
266 }
267 } else {
268 // Non-free cancellation
269 $cancellation_label = __('Cancellation Policy', 'yatra');
270 $cancellation_value = __('See details', 'yatra');
271 }
272 } else {
273 // Default fallback when no policy is set
274 $cancellation_label = __('Cancellation Policy', 'yatra');
275 $cancellation_value = __('May apply', 'yatra');
276 }
277 ?>
278 <div class="yatra-card-info-item">
279 <div class="yatra-card-info-icon">
280 <?php echo yatra_svg_icon('check', 'yatra-icon-sm'); ?>
281 </div>
282 <div class="yatra-card-info-content">
283 <div class="yatra-card-info-label"><?php echo esc_html($cancellation_label); ?></div>
284 <div class="yatra-card-info-value"><?php echo esc_html($cancellation_value); ?></div>
285 </div>
286 </div>
287 </div>
288
289 <div class="yatra-card-traveler-section">
290 <label class="yatra-card-traveler-label"><?php esc_html_e('Travelers', 'yatra'); ?></label>
291
292 <?php
293 // Use card-level pricing type as source of truth
294 $card_pricing_type = $card['pricing_type'] ?? $pricing_type ?? 'regular';
295 $card_price_types = [];
296
297 // Use availability-specific traveler_pricing for traveler-based pricing
298 if ($card_pricing_type === 'traveler_based' && !empty($card['traveler_pricing'])) {
299 $card_price_types = $card['traveler_pricing'];
300 }
301
302 // Normalize traveler pricing to objects if array
303 $normalized_price_types = [];
304 foreach ($card_price_types as $cpt) {
305 if (is_array($cpt)) {
306 $normalized_price_types[] = (object) $cpt;
307 } else {
308 $normalized_price_types[] = $cpt;
309 }
310 }
311 ?>
312
313 <?php if ($card_pricing_type === 'traveler_based' && !empty($normalized_price_types)): ?>
314 <!-- Traveler-based pricing: Show dynamic categories -->
315 <?php
316 // Build traveler rows directly from card-level traveler_pricing data
317 $traveler_rows = [];
318 $display_parts = [];
319 foreach ($normalized_price_types as $pt_index => $pt) {
320 $pt_label = $pt->category_label ?? $pt->label ?? __('Traveler', 'yatra');
321 $pt_pricing_mode = $pt->pricing_mode ?? 'per_person';
322 $pt_is_per_group = ($pt_pricing_mode === 'per_group');
323 $pt_category_id = $pt->category_id ?? $pt_index;
324 $pt_default = $pt_index === 0 ? 1 : 0;
325 $pt_min_qty = 0;
326 $pt_max_qty = (int) min($seats_available, $max_travelers);
327
328 // Determine price with fallback chain
329 $pt_price = 0;
330 if (isset($pt->effective_price) && $pt->effective_price > 0) {
331 $pt_price = (float) $pt->effective_price;
332 } elseif (isset($pt->discounted_price) && $pt->discounted_price > 0) {
333 $pt_price = (float) $pt->discounted_price;
334 } elseif (isset($pt->original_price) && $pt->original_price > 0) {
335 $pt_price = (float) $pt->original_price;
336 }
337
338 // Apply dynamic pricing to traveler category prices
339 if ($dp_enabled && $pt_price > 0) {
340 $pt_orig_dp = (float) ($pt->original_price ?? 0);
341 $pt_disc_dp = (float) ($pt->discounted_price ?? $pt->sale_price ?? $pt->effective_price ?? 0);
342 if ($pt_disc_dp <= 0) {
343 $pt_disc_dp = $pt_price;
344 }
345 $pt_price = apply_filters('yatra_availability_price', $pt_price, $trip_id, [
346 'departure_date' => $card['date'] ?? null,
347 'spots_remaining' => $card['spots_remaining'] ?? null,
348 'availability_id' => $item_id,
349 'price_type_id' => $pt->id ?? ($pt->price_type_id ?? null),
350 'original_price' => $pt_orig_dp > 0 ? $pt_orig_dp : $pt_price,
351 'discounted_price' => $pt_disc_dp > 0 ? $pt_disc_dp : $pt_price,
352 ]);
353 }
354
355 // Age info
356 $pt_age_text = '';
357 $pt_age_min = isset($pt->age_min) ? (int) $pt->age_min : null;
358 $pt_age_max = isset($pt->age_max) ? (int) $pt->age_max : null;
359 if ($pt_age_min !== null || $pt_age_max !== null) {
360 if ($pt_age_min !== null && $pt_age_max !== null) {
361 /* translators: 1: minimum age, 2: maximum age. */
362 $pt_age_text = sprintf(__('(Age %1$d-%2$d)', 'yatra'), $pt_age_min, $pt_age_max);
363 } elseif ($pt_age_min !== null) {
364 /* translators: %d: minimum age. */
365 $pt_age_text = sprintf(__('(Age %d+)', 'yatra'), $pt_age_min);
366 } else {
367 /* translators: %d: maximum age. */
368 $pt_age_text = sprintf(__('(Up to age %d)', 'yatra'), $pt_age_max);
369 }
370 }
371
372 // Build pricing label for per_group mode
373 $pricing_label = '';
374 if ($pt_is_per_group) {
375 if (!empty($pt->min_pax) && !empty($pt->max_pax)) {
376 /* translators: 1: minimum pax for the group price, 2: maximum pax. */
377 $pricing_label = sprintf(__('per group (%1$d-%2$d pax)', 'yatra'), $pt->min_pax, $pt->max_pax);
378 } elseif (!empty($pt->max_pax)) {
379 /* translators: %d: maximum pax for the group price. */
380 $pricing_label = sprintf(__('per group (up to %d pax)', 'yatra'), $pt->max_pax);
381 } elseif (!empty($pt->min_pax)) {
382 /* translators: %d: minimum pax for the group price. */
383 $pricing_label = sprintf(__('per group (%d+ pax)', 'yatra'), $pt->min_pax);
384 } else {
385 $pricing_label = __('per group', 'yatra');
386 }
387 }
388
389 // Build price HTML
390 $pt_price_html = '';
391 if ($pt_price > 0) {
392 $pt_price_html = '<div class="yatra-quantity-price-wrapper">';
393 $pt_price_html .= '<span class="yatra-quantity-price">' . yatra_format_price($pt_price) . '</span>';
394 if ($pt_is_per_group) {
395 $pt_price_html .= '<span class="yatra-pricing-mode-label yatra-pricing-mode-group">' . esc_html($pricing_label) . '</span>';
396 }
397 $pt_price_html .= '</div>';
398 }
399
400 $input_id = 'traveler_' . $pt_category_id;
401
402 $traveler_rows[] = [
403 'label' => $pt_label,
404 'subtitle' => $pt_age_text,
405 'price_html' => $pt_price_html,
406 'row_attrs' => [
407 'data-category-id' => $pt_category_id,
408 'data-price' => $pt_price,
409 'data-pricing-mode' => $pt_pricing_mode,
410 ],
411 'minus_disabled' => $pt_default <= $pt_min_qty,
412 'plus_disabled' => $pt_default >= $pt_max_qty,
413 'minus_attrs' => [
414 'data-target' => $input_id,
415 'data-item' => $item_id,
416 'data-category' => $pt_category_id,
417 /* translators: %s: traveler category label (e.g. "Adult", "Child"). */
418 'aria-label' => sprintf(__('Decrease %s', 'yatra'), $pt_label),
419 ],
420 'plus_attrs' => [
421 'data-target' => $input_id,
422 'data-item' => $item_id,
423 'data-category' => $pt_category_id,
424 /* translators: %s: traveler category label (e.g. "Adult", "Child"). */
425 'aria-label' => sprintf(__('Increase %s', 'yatra'), $pt_label),
426 ],
427 'input_attrs' => [
428 'id' => $input_id,
429 'name' => 'travelers[' . $pt_category_id . ']',
430 'value' => $pt_default,
431 'min' => $pt_min_qty,
432 'max' => $pt_max_qty,
433 'data-item' => $item_id,
434 'data-category' => $pt_category_id,
435 'data-category-label' => $pt_label,
436 'data-price' => $pt_price,
437 'data-pricing-mode' => $pt_pricing_mode,
438 ],
439 ];
440
441 if ($pt_default > 0) {
442 $display_parts[] = $pt_label . ' x ' . $pt_default;
443 }
444 }
445
446 $traveler_display_text = !empty($display_parts) ? implode(', ', $display_parts) : __('Select travelers', 'yatra');
447
448 // Setup variables for traveler-selector.php using availability-specific data
449 $root_id = '';
450 $root_class = 'yatra-booking-field-select yatra-participants-select yatra-availability-participants';
451 $container_attrs = [
452 'data-item' => $item_id,
453 ];
454
455 $display_id = 'participants-display';
456 $display_class = 'yatra-participants-display yatra-availability-participants-display';
457 $display_attrs = [
458 'data-item' => $item_id,
459 ];
460
461 $dropdown_id = '';
462 $dropdown_class = 'yatra-booking-quantity-selector yatra-availability-quantity-selector';
463 $dropdown_attrs = [
464 'data-item' => $item_id,
465 ];
466
467 $display_text = $traveler_display_text;
468 $icon_html = yatra_svg_icon('users', 'yatra-icon-sm');
469 $rows = $traveler_rows;
470
471 include YATRA_PLUGIN_PATH . 'templates/partials/traveler-selector.php';
472 ?>
473 <?php else: ?>
474 <!-- Regular pricing: Show simple number of travelers -->
475 <div class="yatra-booking-travelers-simple yatra-availability-travelers-simple" data-item="<?php echo esc_attr($item_id); ?>">
476 <div class="yatra-booking-field-icon">
477 <?php echo yatra_svg_icon('users', 'yatra-icon-sm'); ?>
478 </div>
479 <div class="yatra-quantity-controls-inline">
480 <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>
481 <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
482 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4"/>
483 </svg>
484 </button>
485 <input type="number"
486 id="num-travelers-<?php echo esc_attr($item_id); ?>"
487 class="yatra-quantity-input-simple yatra-availability-num-travelers"
488 data-item="<?php echo esc_attr($item_id); ?>"
489 value="1"
490 min="1"
491 max="<?php echo esc_attr(min($seats_available, $max_travelers)); ?>"
492 readonly
493 data-price="<?php echo esc_attr($display_sale_price); ?>">
494 <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'); ?>">
495 <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
496 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
497 </svg>
498 </button>
499 </div>
500 <span class="yatra-travelers-label"><?php esc_html_e('travelers', 'yatra'); ?></span>
501 </div>
502 <?php endif; ?>
503
504 <div class="yatra-card-action-help"><?php esc_html_e('Select travelers to update the total, then click Book Now.', 'yatra'); ?></div>
505 </div>
506 </div>
507
508 <!-- Booking Row at Bottom -->
509 <div class="yatra-card-booking-row">
510 <div class="yatra-card-total-box">
511 <div class="yatra-card-total-label"><?php esc_html_e('Total', 'yatra'); ?></div>
512 <div class="yatra-card-total-note" data-item="<?php echo esc_attr($item_id); ?>"><?php esc_html_e('for 1 traveler', 'yatra'); ?></div>
513 <div class="yatra-card-total-amount" data-item="<?php echo esc_attr($item_id); ?>" data-base-price="<?php echo esc_attr($initial_total_price); ?>">
514 <?php
515 $formatted_total = yatra_format_price($initial_total_price);
516 echo esc_html($formatted_total);
517 ?>
518 </div>
519 </div>
520 <?php
521 // Prepare price_types JSON for JavaScript
522 $price_types_json = '';
523 if (!empty($normalized_price_types)) {
524 $price_types_for_js = array_map(function($pt) {
525 $pt = (object) $pt;
526 return [
527 'category_id' => $pt->category_id ?? null,
528 'category_label' => $pt->category_label ?? $pt->label ?? '',
529 'original_price' => $pt->original_price ?? 0,
530 'sale_price' => $pt->sale_price ?? null,
531 'discounted_price' => $pt->discounted_price ?? null,
532 'effective_price' => $pt->effective_price ?? $pt->sale_price ?? $pt->discounted_price ?? $pt->original_price ?? 0,
533 'age_min' => $pt->age_min ?? null,
534 'age_max' => $pt->age_max ?? null,
535 ];
536 }, $normalized_price_types);
537 $price_types_json = wp_json_encode($price_types_for_js);
538 }
539
540 // Get departure time - prefer card data, fallback to from_date if it looks like time
541 $card_departure_time = '';
542 if (!empty($card['departure_time'])) {
543 $card_departure_time = $card['departure_time'];
544 } elseif (!empty($card['from_date']) && preg_match('/^\d{1,2}:\d{2}\s*(AM|PM)?$/i', $card['from_date'])) {
545 $card_departure_time = $card['from_date'];
546 }
547
548 // Is this a day trip?
549 $is_card_day_trip = !empty($card['is_day_trip']) || !empty($card['date_display']);
550 ?>
551 <button type="button"
552 class="yatra-card-book-btn"
553 data-trip-id="<?php echo esc_attr($trip_id); ?>"
554 data-availability-id="<?php echo esc_attr($item_id); ?>"
555 data-date="<?php echo esc_attr($card['data_date'] ?? ''); ?>"
556 data-departure-time="<?php echo esc_attr($card_departure_time); ?>"
557 data-is-day-trip="<?php echo $is_card_day_trip ? '1' : '0'; ?>"
558 data-pricing-type="<?php echo esc_attr($card_pricing_type); ?>"
559 data-price-types="<?php echo esc_attr($price_types_json); ?>"
560 data-price="<?php echo esc_attr($display_sale_price); ?>"
561 data-item="<?php echo esc_attr($item_id); ?>">
562 <?php echo yatra_svg_icon('shopping-cart', 'yatra-icon-sm'); ?>
563 <span class="yatra-card-book-btn-text">
564 <span class="yatra-card-book-btn-title"><?php esc_html_e('Book Now', 'yatra'); ?></span>
565 <span class="yatra-card-book-btn-subtitle"><?php esc_html_e('Continue to booking', 'yatra'); ?></span>
566 </span>
567 </button>
568 </div>
569 </div>
570 </div>
571