Plugin Directory

Changeset 3431070


Ignore:
Timestamp:
01/02/2026 01:48:09 PM (3 months ago)
Author:
creavi
Message:

Added dynamic availability mode

Location:
creavi-booking-service
Files:
27 added
9 edited

Legend:

Unmodified
Added
Removed
  • creavi-booking-service/trunk/assets/css/admin.css

    r3342870 r3431070  
    267267    fill: #007cba;
    268268}
     269
     270
     271/* Tabs wrapper */
     272.creavibc-tabs {
     273  margin-top: 6px;
     274}
     275
     276.creavibc-tabs-head {
     277  display: flex;
     278  gap: 16px;
     279  align-items: center;
     280  justify-content: space-between;
     281  padding: 14px 14px;
     282  background: #fff;
     283  border-radius: 12px;
     284}
     285
     286.creavibc-tabs-title {
     287  font-size: 14px;
     288  font-weight: 700;
     289  color: #111827;
     290  margin-bottom: 2px;
     291}
     292
     293.creavibc-tabs-subtitle {
     294  font-size: 12px;
     295  color: #6b7280;
     296}
     297
     298/* Tab buttons */
     299.creavibc-tab-nav {
     300  display: inline-flex;
     301  background: #f3f4f6;
     302  border-radius: 999px;
     303  padding: 4px;
     304  border: 1px solid #e5e7eb;
     305}
     306
     307.creavibc-tab {
     308  appearance: none;
     309  border: 0;
     310  background: transparent;
     311  cursor: pointer;
     312  padding: 8px 12px;
     313  border-radius: 999px;
     314  font-weight: 700;
     315  font-size: 12px;
     316  color: #374151;
     317  display: inline-flex;
     318  align-items: baseline;
     319  gap: 8px;
     320  transition: all .15s ease;
     321}
     322
     323.creavibc-tab .creavibc-tab-hint {
     324  font-weight: 600;
     325  font-size: 11px;
     326  color: #6b7280;
     327}
     328
     329.creavibc-tab.is-active {
     330  background: #fff;
     331  box-shadow: 0 1px 0 rgba(17, 24, 39, .04), 0 1px 2px rgba(17, 24, 39, .08);
     332  color: #111827;
     333}
     334
     335.creavibc-tab.is-active .creavibc-tab-hint {
     336  color: #374151;
     337}
     338
     339/* Panels */
     340.creavibc-tab-panels {
     341  margin-top: 12px;
     342}
     343
     344.creavibc-tab-panel { display: none; }
     345.creavibc-tab-panel.is-active { display: block; }
     346
     347.creavibc-panel-card { 
     348  background: #fff;
     349  border-radius: 12px;
     350  padding: 14px;
     351}
     352
     353.creavibc-panel-title {
     354  font-size: 13px;
     355  font-weight: 700;
     356  color: #111827;
     357  margin-bottom: 6px;
     358}
     359
     360.creavibc-help {
     361  font-size: 12px;
     362  color: #6b7280;
     363  margin: 0 0 12px 0;
     364}
     365
     366.creavibc-field {
     367  margin-bottom: 14px;
     368}
     369
     370.creavibc-input,
     371.creavibc-select {
     372  width: 50%;
     373  max-width: 520px;
     374  padding: 8px 10px;
     375  border-radius: 10px;
     376  border: 1px solid #d1d5db;
     377  background: #fff;
     378}
     379
     380.creavibc-input:focus,
     381.creavibc-select:focus {
     382  outline: none;
     383  border-color: #60a5fa;
     384  box-shadow: 0 0 0 3px rgba(96, 165, 250, .25);
     385}
  • creavi-booking-service/trunk/assets/js/admin.js

    r3357718 r3431070  
    617617    });
    618618});
     619
     620document.addEventListener('DOMContentLoaded', function () {
     621  const panel = document.getElementById("creavibc-dynamic-days-settings");
     622  const radios = document.querySelectorAll('input[name="creavibc_availability_mode"]');
     623
     624  if (!panel || !radios.length) return;
     625
     626  function sync() {
     627    const checked = document.querySelector('input[name="creavibc_availability_mode"]:checked');
     628    const val = checked ? checked.value : "static";
     629    panel.style.display = (val === "dynamic") ? "block" : "none";
     630  }
     631
     632  radios.forEach(r => r.addEventListener("change", sync));
     633  sync();
     634});
     635
     636
     637document.addEventListener('DOMContentLoaded', function () {
     638  const excludeField = document.getElementById('creavibc_excluded_booking_days');
     639  if (!excludeField || typeof flatpickr === 'undefined') return;
     640
     641  const defaultExcluded = excludeField.value
     642    ? excludeField.value.split(',').map(s => s.trim()).filter(Boolean)
     643    : [];
     644
     645  const fpExclude = flatpickr(excludeField, {
     646    mode: 'multiple',
     647    dateFormat: 'Y-m-d',
     648    defaultDate: defaultExcluded,
     649    closeOnSelect: false,
     650    onChange: function (selectedDates, dateStr, instance) {
     651      const formatted = selectedDates
     652        .map(date => instance.formatDate(date, 'Y-m-d'))
     653        .join(',');
     654      excludeField.value = formatted;
     655      excludeField.dispatchEvent(new Event('input', { bubbles: true }));
     656    }
     657  });
     658});
     659
     660
     661document.addEventListener('DOMContentLoaded', function () {
     662  const radios = document.querySelectorAll('input[name="creavibc_availability_mode"]');
     663  const panel = document.getElementById('creavibc-dynamic-days-settings');
     664  if (!radios.length || !panel) return;
     665
     666  function sync() {
     667    const val = document.querySelector('input[name="creavibc_availability_mode"]:checked')?.value || 'static';
     668    panel.style.display = (val === 'dynamic') ? 'block' : 'none';
     669  }
     670
     671  radios.forEach(r => r.addEventListener('change', sync));
     672  sync();
     673});
     674
     675document.addEventListener('DOMContentLoaded', function () {
     676  const root = document.querySelector('.creavibc-tabs');
     677  if (!root) return;
     678
     679  const hidden = document.getElementById('creavibc_availability_mode');
     680  const tabs = root.querySelectorAll('.creavibc-tab');
     681  const panels = root.querySelectorAll('.creavibc-tab-panel');
     682
     683  function setActive(mode) {
     684    if (!hidden) return;
     685    hidden.value = mode;
     686
     687    tabs.forEach(btn => {
     688      const isActive = btn.dataset.tab === mode;
     689      btn.classList.toggle('is-active', isActive);
     690      btn.setAttribute('aria-selected', isActive ? 'true' : 'false');
     691    });
     692
     693    panels.forEach(panel => {
     694      panel.classList.toggle('is-active', panel.dataset.panel === mode);
     695    });
     696  }
     697
     698  tabs.forEach(btn => {
     699    btn.addEventListener('click', function () {
     700      setActive(btn.dataset.tab);
     701    });
     702  });
     703
     704  // init based on saved value
     705  setActive(hidden?.value || root.dataset.default || 'static');
     706});
     707
  • creavi-booking-service/trunk/assets/js/booking.js

    r3347062 r3431070  
    233233    }
    234234
     235        // -----------------------------
     236    // Dynamic availability helpers
     237    // -----------------------------
     238    function creavibc_normalizeCsvDates(csv) {
     239        if (!csv) return [];
     240        return String(csv).split(',')
     241            .map(s => (s || '').trim())
     242            .filter(Boolean);
     243    }
     244
     245    function creavibc_adminDateToUserDate(adminDateStr, adminTz, userTz) {
     246        // Use noon to avoid DST edge issues when converting date-only values
     247        return luxon.DateTime
     248            .fromISO(adminDateStr, { zone: adminTz })
     249            .set({ hour: 12, minute: 0, second: 0, millisecond: 0 })
     250            .setZone(userTz)
     251            .toISODate();
     252    }
     253
     254    function creavibc_generateDynamicAvailableDates({ monthsAhead, adminTz, tzMode, excludedCsv }) {
     255        const userTz = window.CREAVIBC_USER_TIMEZONE || Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
     256        const safeMonths = Math.max(1, Math.min(12, parseInt(monthsAhead, 10) || 1));
     257
     258        // Generate in admin tz (source of truth)
     259        const startAdmin = luxon.DateTime.now().setZone(adminTz).startOf('day');
     260        const endAdmin = startAdmin.plus({ months: safeMonths }).startOf('day');
     261
     262        // Excluded dates set (stored as admin dates)
     263        const excludedAdmin = new Set(creavibc_normalizeCsvDates(excludedCsv));
     264        const excludedForPicker = new Set();
     265
     266        if (tzMode === 'localized' && userTz !== adminTz) {
     267            excludedAdmin.forEach(d => excludedForPicker.add(creavibc_adminDateToUserDate(d, adminTz, userTz)));
     268        } else {
     269            excludedAdmin.forEach(d => excludedForPicker.add(d));
     270        }
     271
     272        const out = [];
     273
     274        for (let cursor = startAdmin; cursor <= endAdmin; cursor = cursor.plus({ days: 1 })) {
     275            const adminDate = cursor.toISODate();
     276
     277            const dateForPicker =
     278                (tzMode === 'localized' && userTz !== adminTz)
     279                    ? creavibc_adminDateToUserDate(adminDate, adminTz, userTz)
     280                    : adminDate;
     281
     282            if (!excludedForPicker.has(dateForPicker)) {
     283                out.push(dateForPicker);
     284            }
     285        }
     286
     287        return out;
     288    }
     289
     290
    235291    function initInlineCalendar(serviceId) {
    236292        const calendarEl = document.querySelector(`.creavibc-datepicker-inline[data-service-id="${serviceId}"]`);
     
    244300
    245301        // Sort AVAILABLE_DATES and prefer the first date >= today
    246         const dates = (instance.AVAILABLE_DATES || []).slice().sort();
     302        //const dates = (instance.AVAILABLE_DATES || []).slice().sort();
     303        //const todayStr = new Date().toISOString().slice(0, 10);
     304        //const firstEnabled = dates.find(d => d >= todayStr) || dates[0] || null;
     305
     306        // Build enabled dates: static (existing) OR dynamic (generated)
     307        let dates = (instance.AVAILABLE_DATES || []).slice();
     308
     309        if (instance.AVAILABILITY_MODE === 'dynamic' && window.luxon) {
     310            dates = creavibc_generateDynamicAvailableDates({
     311                monthsAhead: instance.MONTHS_AHEAD || 1,
     312                adminTz: instance.ADMIN_TIMEZONE || 'UTC',
     313                tzMode: instance.TIMEZONE_MODE || 'localized',
     314                excludedCsv: instance.EXCLUDED_DATES || ''
     315            });
     316        }
     317
     318        dates = dates.slice().sort();
     319
    247320        const todayStr = new Date().toISOString().slice(0, 10);
    248321        const firstEnabled = dates.find(d => d >= todayStr) || dates[0] || null;
     322
     323
    249324
    250325        const fp = flatpickr(calendarEl, {
  • creavi-booking-service/trunk/creavi-booking-service.php

    r3358416 r3431070  
    2424require_once CREAVIBC_PLUGIN_DIR . 'includes/render-booking-inline.php';
    2525require_once CREAVIBC_PLUGIN_DIR . 'includes/admin.php';
     26//require_once CREAVIBC_PLUGIN_DIR . 'includes/freebusy.php';
    2627require_once CREAVIBC_PLUGIN_DIR . 'includes/ajax-handlers.php';
    2728
  • creavi-booking-service/trunk/includes/functions.php

    r3342870 r3431070  
    6868    $brand_color = get_post_meta($post->ID, '_creavibc_primary_color', true) ?: '#569FF7';
    6969
     70    $availability_mode = get_post_meta($post->ID, '_creavibc_availability_mode', true) ?: 'static';
     71    $months_ahead      = (int) get_post_meta($post->ID, '_creavibc_dynamic_months_ahead', true);
     72    if ($months_ahead < 1) $months_ahead = 1;
     73    if ($months_ahead > 12) $months_ahead = 12;
     74
     75    $excluded = get_post_meta($post->ID, '_creavibc_excluded_booking_days', true) ?: '';
     76
     77
    7078
    7179    global $creavibc_instances;
     
    7886    'ADMIN_TIMEZONE'   => $tz_iana,
    7987    'TIMEZONE_MODE'    => $tz_mode,
     88
     89    'AVAILABILITY_MODE'   => $availability_mode,     // static|dynamic
     90    'MONTHS_AHEAD'        => $months_ahead,           // 1..12
     91    'EXCLUDED_DATES'      => $excluded,               // "YYYY-MM-DD,YYYY-MM-DD"
    8092    ];
    8193
  • creavi-booking-service/trunk/includes/meta-boxes.php

    r3358416 r3431070  
    9797
    9898
    99 
     99/*
    100100function creavibc_render_available_days_box($post) {
    101101    $date_value = get_post_meta($post->ID, '_creavibc_available_booking_days', true);
     
    119119    echo '<input type="text" id="creavibc_available_booking_days" name="creavibc_available_booking_days" value="' . $dates_string . '" style="width:100%; padding: 10px; font-size: 16px;" autocomplete="off">';
    120120    echo '<p class="description">Click inside the field to manually select or edit available dates. You can also use the range selector above to quickly generate a full list of future dates.</p>';
     121}*/
     122/*
     123function creavibc_render_available_days_box($post) {
     124
     125    // Existing saved static dates (keep as-is)
     126    $date_value   = get_post_meta($post->ID, '_creavibc_available_booking_days', true);
     127    $dates_array  = is_array($date_value) ? $date_value : explode(',', (string) $date_value);
     128    $dates_string = esc_attr(implode(',', array_filter(array_map('trim', $dates_array))));
     129
     130    // Availability mode
     131    $mode = get_post_meta($post->ID, '_creavibc_availability_mode', true);
     132    if ( ! in_array($mode, ['static', 'dynamic'], true) ) {
     133        $mode = 'static';
     134    }
     135
     136    // Dynamic settings
     137    $months_ahead = (int) get_post_meta($post->ID, '_creavibc_dynamic_months_ahead', true);
     138    if ($months_ahead < 1)  $months_ahead = 1;
     139    if ($months_ahead > 12) $months_ahead = 12;
     140
     141    $exclude_value  = get_post_meta($post->ID, '_creavibc_excluded_booking_days', true);
     142    $exclude_array  = is_array($exclude_value) ? $exclude_value : explode(',', (string) $exclude_value);
     143    $exclude_string = esc_attr(implode(',', array_filter(array_map('trim', $exclude_array))));
     144
     145    wp_nonce_field('creavibc_save_days', 'creavibc_days_nonce');
     146
     147    // --- Mode switch
     148    echo '<div style="margin-bottom:14px; padding:12px; border:1px solid #e5e5e5; border-radius:8px; background:#fff;">';
     149    echo '<p style="margin:0 0 8px 0;"><strong>How do you want to define available days?</strong></p>';
     150
     151    echo '<label style="display:block; margin:0 0 8px 0;">';
     152    echo '<input type="radio" name="creavibc_availability_mode" value="static" ' . checked($mode, 'static', false) . ' /> ';
     153    echo '<strong>Static</strong> — You manually pick exact bookable dates.';
     154    echo '</label>';
     155
     156    echo '<label style="display:block; margin:0;">';
     157    echo '<input type="radio" name="creavibc_availability_mode" value="dynamic" ' . checked($mode, 'dynamic', false) . ' /> ';
     158    echo '<strong>Dynamic</strong> — Customers can book in a rolling range from today (e.g. next 3 months), with optional exclusions.';
     159    echo '</label>';
     160
     161    //echo '<p class="description" style="margin:8px 0 0 0;">Tip: Start with Dynamic for a SaaS-like experience. Use exclusions for vacations, holidays, blocked days.</p>';
     162    echo '</div>';
     163
     164    // --- Dynamic panel
     165    $dynamic_style = ($mode === 'dynamic') ? '' : 'display:none;';
     166    echo '<div id="creavibc-dynamic-days-settings" style="margin-bottom:14px; padding:12px; border:1px solid #e5e5e5; border-radius:8px; background:#fff; ' . esc_attr($dynamic_style) . '">';
     167
     168    echo '<p style="margin:0 0 10px 0;"><strong>Dynamic availability settings</strong></p>';
     169
     170    echo '<p style="margin:0 0 12px 0;">';
     171    echo '<label for="creavibc_dynamic_months_ahead"><strong>Booking window (months ahead)</strong></label><br>';
     172    echo '<span class="description">Example: “3” means customers can book from today until 3 months from now.</span><br>';
     173    echo '<select name="creavibc_dynamic_months_ahead" id="creavibc_dynamic_months_ahead" style="margin-top:6px;">';
     174    for ($i = 1; $i <= 12; $i++) {
     175        $label = $i . ' month' . ($i > 1 ? 's' : '');
     176        echo '<option value="' . esc_attr($i) . '" ' . selected($months_ahead, $i, false) . '>' . esc_html($label) . '</option>';
     177    }
     178    echo '</select>';
     179    echo '</p>';
     180
     181    echo '<p style="margin:0;">';
     182    echo '<label for="creavibc_excluded_booking_days"><strong>Excluded dates (not bookable)</strong></label><br>';
     183    echo '<span class="description">Pick one or multiple dates to block (vacations, holidays, fully booked days). Stored as comma-separated YYYY-MM-DD.</span><br>';
     184    echo '<input type="text" id="creavibc_excluded_booking_days" name="creavibc_excluded_booking_days" value="' . $exclude_string . '" style="width:100%; padding:8px; margin-top:6px;" autocomplete="off">';
     185    echo '</p>';
     186
     187    echo '</div>';
     188
     189    // --- Static UI (keep behavior)
     190    echo '<div style="padding:12px; border:1px solid #e5e5e5; border-radius:8px; background:#fff;">';
     191
     192    echo '<p style="margin:0 0 8px 0;"><strong>Static availability (manual dates)</strong></p>';
     193    echo '<label for="creavibc_range_selector"><strong>Quick generate range</strong></label><br>';
     194    echo '<span class="description">This fills the static field below with all dates from today up to the chosen range.</span>';
     195
     196    echo '<div style="margin-top:6px;">';
     197    echo '<select id="creavibc_range_selector">';
     198    echo '<option value="">— Select a Range —</option>';
     199    for ($i = 1; $i <= 12; $i++) {
     200        $label = $i . ' month' . ($i > 1 ? 's' : '');
     201        echo '<option value="' . esc_attr($i) . '">' . esc_html($label . ' ahead') . '</option>';
     202    }
     203    echo '</select>';
     204    echo '</div>';
     205
     206    echo '<p style="margin:14px 0 6px 0;"><strong>Bookable dates</strong></p>';
     207    echo '<span class="description">Click the input to select multiple dates. Stored as comma-separated YYYY-MM-DD.</span><br>';
     208    echo '<input type="text" id="creavibc_available_booking_days" name="creavibc_available_booking_days" value="' . $dates_string . '" style="width:100%; padding:10px; font-size:16px; margin-top:6px;" autocomplete="off">';
     209
     210    echo '</div>';
     211}
     212*/
     213
     214function creavibc_render_available_days_box($post) {
     215
     216    // Existing saved static dates (keep as-is)
     217    $date_value   = get_post_meta($post->ID, '_creavibc_available_booking_days', true);
     218    $dates_array  = is_array($date_value) ? $date_value : explode(',', (string) $date_value);
     219    $dates_string = esc_attr(implode(',', array_filter(array_map('trim', $dates_array))));
     220
     221    // Availability mode + dynamic settings
     222    $mode = get_post_meta($post->ID, '_creavibc_availability_mode', true);
     223    if ( ! in_array($mode, ['static', 'dynamic'], true) ) {
     224        $mode = 'static';
     225    }
     226
     227    $months_ahead = (int) get_post_meta($post->ID, '_creavibc_dynamic_months_ahead', true);
     228    $months_ahead = max(1, min(12, $months_ahead ?: 1));
     229
     230    // ✅ Canonical excluded field name + meta key (do not change!)
     231    $excluded_value  = (string) get_post_meta($post->ID, '_creavibc_excluded_booking_days', true);
     232    $excluded_array  = array_filter(array_map('trim', explode(',', $excluded_value)));
     233    $excluded_string = esc_attr(implode(',', $excluded_array));
     234
     235    wp_nonce_field('creavibc_save_days', 'creavibc_days_nonce');
     236
     237    $is_dynamic = ($mode === 'dynamic');
     238    ?>
     239
     240    <div class="creavibc-tabs" data-default="<?php echo esc_attr($mode); ?>">
     241        <!-- Hidden field: this is what gets saved -->
     242        <input type="hidden" name="creavibc_availability_mode" id="creavibc_availability_mode" value="<?php echo esc_attr($mode); ?>">
     243
     244        <!-- Header -->
     245        <div class="creavibc-tabs-head">
     246            <div>
     247                <div class="creavibc-tabs-title"><?php esc_html_e('Available Booking Days', 'creavi-booking-service'); ?></div>
     248                <div class="creavibc-tabs-subtitle">
     249                    <?php esc_html_e('Choose how customers can book: rolling window (dynamic) or manually selected dates (static).', 'creavi-booking-service'); ?>
     250                </div>
     251            </div>
     252
     253            <!-- Tab buttons -->
     254            <div class="creavibc-tab-nav" role="tablist" aria-label="<?php esc_attr_e('Availability mode', 'creavi-booking-service'); ?>">
     255                <button
     256                    type="button"
     257                    class="creavibc-tab <?php echo $is_dynamic ? 'is-active' : ''; ?>"
     258                    data-tab="dynamic"
     259                    role="tab"
     260                    aria-selected="<?php echo $is_dynamic ? 'true' : 'false'; ?>"
     261                >
     262                    <?php esc_html_e('Dynamic', 'creavi-booking-service'); ?>
     263                    <span class="creavibc-tab-hint"><?php esc_html_e('Rolling window', 'creavi-booking-service'); ?></span>
     264                </button>
     265
     266                <button
     267                    type="button"
     268                    class="creavibc-tab <?php echo ! $is_dynamic ? 'is-active' : ''; ?>"
     269                    data-tab="static"
     270                    role="tab"
     271                    aria-selected="<?php echo ! $is_dynamic ? 'true' : 'false'; ?>"
     272                >
     273                    <?php esc_html_e('Static', 'creavi-booking-service'); ?>
     274                    <span class="creavibc-tab-hint"><?php esc_html_e('Manual dates', 'creavi-booking-service'); ?></span>
     275                </button>
     276            </div>
     277        </div>
     278
     279        <!-- Panels -->
     280        <div class="creavibc-tab-panels">
     281            <!-- Dynamic panel -->
     282            <div class="creavibc-tab-panel <?php echo $is_dynamic ? 'is-active' : ''; ?>" data-panel="dynamic" role="tabpanel">
     283                <div class="creavibc-panel-card">
     284                    <div class="creavibc-panel-title"><?php esc_html_e('Dynamic availability settings', 'creavi-booking-service'); ?></div>
     285                    <p class="creavibc-help">
     286                        <?php esc_html_e('Customers can book from today up to the selected window. You can also block specific dates (vacations, holidays, fully booked days).', 'creavi-booking-service'); ?>
     287                    </p>
     288
     289                    <div class="creavibc-field">
     290                        <label for="creavibc_dynamic_months_ahead"><strong><?php esc_html_e('Booking window (months ahead)', 'creavi-booking-service'); ?></strong></label>
     291                        <div class="creavibc-help"><?php esc_html_e('Example: “3” means customers can book from today until 3 months from now.', 'creavi-booking-service'); ?></div>
     292                        <select name="creavibc_dynamic_months_ahead" id="creavibc_dynamic_months_ahead" class="creavibc-select">
     293                            <?php for ($i = 1; $i <= 12; $i++): ?>
     294                                <option value="<?php echo esc_attr($i); ?>" <?php selected($months_ahead, $i); ?>>
     295                                    <?php echo esc_html($i . ' ' . __('month', 'creavi-booking-service') . ($i > 1 ? 's' : '')); ?>
     296                                </option>
     297                            <?php endfor; ?>
     298                        </select>
     299                    </div>
     300
     301                    <div class="creavibc-field">
     302                        <label for="creavibc_excluded_booking_days"><strong><?php esc_html_e('Excluded dates (not bookable)', 'creavi-booking-service'); ?></strong></label>
     303                        <div class="creavibc-help"><?php esc_html_e('Pick one or multiple dates to block. Stored as comma-separated YYYY-MM-DD.', 'creavi-booking-service'); ?></div>
     304
     305                        <!-- ✅ Keep the SAME ID + NAME so flatpickr init & POST save match -->
     306                        <input
     307                            type="text"
     308                            id="creavibc_excluded_booking_days"
     309                            name="creavibc_excluded_booking_days"
     310                            value="<?php echo $excluded_string; ?>"
     311                            class="creavibc-input"
     312                            autocomplete="off"
     313                            placeholder="YYYY-MM-DD, YYYY-MM-DD"
     314                        >
     315                    </div>
     316
     317                    <div class="creavibc-help" style="margin-top:10px;">
     318                        <?php esc_html_e('Tip: Excluded dates are applied only when Dynamic is selected.', 'creavi-booking-service'); ?>
     319                    </div>
     320                </div>
     321            </div>
     322
     323            <!-- Static panel -->
     324            <div class="creavibc-tab-panel <?php echo ! $is_dynamic ? 'is-active' : ''; ?>" data-panel="static" role="tabpanel">
     325                <div class="creavibc-panel-card">
     326                    <div class="creavibc-panel-title"><?php esc_html_e('Static availability (manual dates)', 'creavi-booking-service'); ?></div>
     327                    <p class="creavibc-help"><?php esc_html_e('Select exact bookable dates. Use the quick range generator or pick manually.', 'creavi-booking-service'); ?></p>
     328
     329                    <div class="creavibc-field">
     330                        <label for="creavibc_range_selector"><strong><?php esc_html_e('Quick generate range', 'creavi-booking-service'); ?></strong></label>
     331                        <div class="creavibc-help"><?php esc_html_e('Fills the static field below with all dates from today up to the chosen range.', 'creavi-booking-service'); ?></div>
     332
     333                        <select id="creavibc_range_selector" class="creavibc-select">
     334                            <option value=""><?php esc_html_e('— Select a Range —', 'creavi-booking-service'); ?></option>
     335                            <?php for ($i = 1; $i <= 12; $i++): ?>
     336                                <option value="<?php echo esc_attr($i); ?>">
     337                                    <?php echo esc_html($i . ' Month' . ($i > 1 ? 's' : '') . ' Ahead'); ?>
     338                                </option>
     339                            <?php endfor; ?>
     340                        </select>
     341                    </div>
     342
     343                    <div class="creavibc-field">
     344                        <label for="creavibc_available_booking_days"><strong><?php esc_html_e('Available booking dates', 'creavi-booking-service'); ?></strong></label>
     345                        <div class="creavibc-help"><?php esc_html_e('Click inside to pick dates. Stored as comma-separated YYYY-MM-DD.', 'creavi-booking-service'); ?></div>
     346
     347                        <input
     348                            type="text"
     349                            id="creavibc_available_booking_days"
     350                            name="creavibc_available_booking_days"
     351                            value="<?php echo $dates_string; ?>"
     352                            class="creavibc-input"
     353                            autocomplete="off"
     354                            placeholder="YYYY-MM-DD, YYYY-MM-DD"
     355                        >
     356                    </div>
     357                </div>
     358            </div>
     359        </div>
     360
     361     
     362    </div>
     363
     364    <?php
    121365}
    122366
  • creavi-booking-service/trunk/includes/render-booking-inline.php

    r3347062 r3431070  
    5050    $tz_mode      = get_post_meta($post->ID, '_creavibc_timezone_mode', true) ?: 'localized';
    5151    $brand_color  = get_post_meta($post->ID, '_creavibc_primary_color', true) ?: '#569FF7';
     52
     53    $availability_mode = get_post_meta($post->ID, '_creavibc_availability_mode', true) ?: 'static';
     54    $months_ahead      = (int) get_post_meta($post->ID, '_creavibc_dynamic_months_ahead', true);
     55    if ($months_ahead < 1) $months_ahead = 1;
     56    if ($months_ahead > 12) $months_ahead = 12;
     57
     58    $excluded = get_post_meta($post->ID, '_creavibc_excluded_booking_days', true) ?: '';
     59
    5260
    5361    global $creavibc_instances;
     
    5967        'ADMIN_TIMEZONE'   => $tz_iana,
    6068        'TIMEZONE_MODE'    => $tz_mode,
     69       
     70        'AVAILABILITY_MODE'   => $availability_mode,     // static|dynamic
     71        'MONTHS_AHEAD'        => $months_ahead,           // 1..12
     72        'EXCLUDED_DATES'      => $excluded,               // "YYYY-MM-DD,YYYY-MM-DD"
    6173    ];
    6274    ?>
  • creavi-booking-service/trunk/includes/save-service.php

    r3352052 r3431070  
    88    // Save available days
    99   
     10    /*
    1011    $days_nonce = isset($_POST['creavibc_days_nonce']) ? sanitize_text_field(wp_unslash($_POST['creavibc_days_nonce'])) : '';
    1112    if ($days_nonce && wp_verify_nonce($days_nonce, 'creavibc_save_days')) {
     
    1617
    1718        }
    18     }
     19    }*/
     20
     21    $days_nonce = isset($_POST['creavibc_days_nonce']) ? sanitize_text_field(wp_unslash($_POST['creavibc_days_nonce'])) : '';
     22    if ( $days_nonce && wp_verify_nonce( $days_nonce, 'creavibc_save_days' ) ) {
     23
     24        // 1) Static dates (existing)
     25        if ( isset($_POST['creavibc_available_booking_days']) ) {
     26            $raw = sanitize_text_field( wp_unslash( $_POST['creavibc_available_booking_days'] ) );
     27            // Normalize: remove spaces around commas
     28            $raw = implode(',', array_filter(array_map('trim', explode(',', $raw))));
     29            update_post_meta( $post_id, '_creavibc_available_booking_days', $raw );
     30        }
     31
     32        // 2) Availability mode (new)
     33        $mode = isset($_POST['creavibc_availability_mode'])
     34            ? sanitize_text_field( wp_unslash($_POST['creavibc_availability_mode']) )
     35            : 'static';
     36
     37        $mode = in_array($mode, ['static','dynamic'], true) ? $mode : 'static';
     38        update_post_meta( $post_id, '_creavibc_availability_mode', $mode );
     39
     40        // 3) Months ahead (new)
     41        $months = isset($_POST['creavibc_dynamic_months_ahead'])
     42            ? (int) $_POST['creavibc_dynamic_months_ahead']
     43            : 1;
     44
     45        if ( $months < 1 )  $months = 1;
     46        if ( $months > 12 ) $months = 12;
     47
     48        update_post_meta( $post_id, '_creavibc_dynamic_months_ahead', $months );
     49
     50        // 4) Excluded dates (new)
     51        if ( isset($_POST['creavibc_excluded_booking_days']) ) {
     52            $exclude_raw = sanitize_text_field( wp_unslash($_POST['creavibc_excluded_booking_days']) );
     53
     54            $exclude_dates = array_filter(array_map('trim', explode(',', $exclude_raw)));
     55            // Keep only valid YYYY-MM-DD
     56            $exclude_dates = array_values(array_filter($exclude_dates, function($d){
     57                return preg_match('/^\d{4}-\d{2}-\d{2}$/', $d);
     58            }));
     59
     60            update_post_meta( $post_id, '_creavibc_excluded_booking_days', implode(',', $exclude_dates) );
     61        } else {
     62            // If field missing, keep existing meta (don’t delete)
     63            // (Do nothing)
     64        }
     65    }
     66
    1967
    2068    // Save form fields
  • creavi-booking-service/trunk/readme.txt

    r3423763 r3431070  
    55Tested up to: 6.8 
    66Requires PHP: 7.4 
    7 Stable tag: 1.0.17
     7Stable tag: 1.1.0
    88License: GPLv2 or later 
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    8686== Changelog ==
    8787
     88= 1.1.0 =
     89* Added dynamic availability mode with rolling booking windows and support for excluded dates.
     90* Improved Available Booking Days UI with a tab-based switch between dynamic and static modes, fully backward compatible.
    8891
    8992= 1.0.17 =
     
    9396* Fixed admin timezone selector to correctly keep the saved value selected on post edit.
    9497* Improved timezone dropdown population to show the full list while preserving the stored timezone.
    95 
    9698
    9799= 1.0.15 =
     
    105107* Added smooth animations when rendering or clearing time slots
    106108* Improved booking slots container with height transition to prevent layout jumps
    107 
    108109
    109110= 1.0.12 =
Note: See TracChangeset for help on using the changeset viewer.