YITH WooCommerce Resources Dates
August 7, 2025 · AI Conversations
—
### ✅ Goal:
> For a **product (booking product)**, get each **attached resource** and its **available dates** programmatically.
—
### 🔍 Step-by-Step Breakdown:
#### 1. **Get Booking Product Object**
You start by getting the product (ensure it’s a booking product):
$booking_product = wc_get_product($product_id);
if ( ! is_a($booking_product, 'YITH_WCBK_Product_Booking') ) {
$booking_product = yith_get_booking_product( $product_id );
}
—
#### 2. **Get Attached Resources**
Each booking product can have multiple resources attached:
$resources = yith_wcbk_get_product_resources( $product_id ); // Returns array of resource IDs
—
#### 3. **Loop through Resources and Get Available Dates**
For each resource, you can use `YITH_WCBK_Availability` to calculate availability per date.
Here’s a simplified version:
foreach ( $resources as $resource_id ) {
$resource = yith_wcbk_get_resource( $resource_id );
if ( ! $resource ) continue;
echo 'Resource: ' . $resource->get_name() . '<br>';
// Get availability for a range of dates
$start_date = strtotime( 'today' );
$end_date = strtotime( '+30 days' ); // Next 30 days
for ( $date = $start_date; $date <= $end_date; $date += DAY_IN_SECONDS ) {
$available = YITH_WCBK_Availability::is_bookable(
$product_id,
[
'check_in_date' => $date,
'check_out_date' => $date + DAY_IN_SECONDS,
'resource_id' => $resource_id
]
);
if ( $available ) {
echo ' - Available on: ' . date( 'Y-m-d', $date ) . '<br>';
}
}
}
—
### 📌 Notes:
– `YITH_WCBK_Availability::is_bookable()` checks if the product is available with the given resource and date.
– You can modify the range (`+30 days`) or the granularity (e.g., hourly if needed).
– This example assumes **daily availability** — adapt it if your booking setup uses hourly/multi-day blocks.
—
### 🧠 Pro Tip:
If you want performance optimization, use `YITH_WCBK_Product_Booking->get_resources()` to get resource objects directly:
$booking_product = yith_get_booking_product($product_id);
$resources = $booking_product->get_resources();
Then call `$resource->get_id()` or `$resource->get_name()`.
—
Would you like me to output this as JSON format (product → resource → dates)?
The function `yith_wcbk_get_product_resources()` **does not exist** in the YITH Booking Premium plugin. Instead, you should use the **object-oriented method** on the booking product.
—
### ✅ Correct Way to Get Attached Resources
Use this instead:
$product = yith_get_booking_product( $product_id );
if ( $product && $product->has_resources() ) {
$resources = $product->get_resources(); // Returns array of YITH_WCBK_Product_Resource objects
}
Then loop over the resources:
foreach ( $resources as $resource ) {
$resource_id = $resource->get_id();
$resource_name = $resource->get_name();
echo 'Resource: ' . $resource_name . '<br>';
// Check availability for next 30 days
$start_date = strtotime( 'today' );
$end_date = strtotime( '+30 days' );
for ( $date = $start_date; $date <= $end_date; $date += DAY_IN_SECONDS ) {
$available = YITH_WCBK_Availability::is_bookable(
$product_id,
[
'check_in_date' => $date,
'check_out_date' => $date + DAY_IN_SECONDS,
'resource_id' => $resource_id
]
);
if ( $available ) {
echo ' - Available on: ' . date( 'Y-m-d', $date ) . '<br>';
}
}
}
—
### 🔍 Explanation:
– `yith_get_booking_product()` ensures you get a `YITH_WCBK_Product_Booking` object.
– `->get_resources()` gives you the actual **resource objects** (not just IDs).
– `YITH_WCBK_Availability::is_bookable()` checks availability per date and resource.
—
Would you like this in a reusable function format or JSON output?
The function `yith_get_booking_product()` is **not globally available** unless YITH Booking Premium plugin classes are loaded and the product is indeed a **booking product**.
—
### ✅ Correct & Safe Way to Get a Booking Product with Resources
Use this complete flow to avoid any `undefined function` or class errors:
—
### ✅ Full Working Example (Safe & Compatible):
$product_id = 123; // Replace with your booking product ID
$product = wc_get_product( $product_id );
// Ensure it's a booking product with resource support
if ( $product && class_exists( 'YITH_WCBK_Product_Booking' ) && $product instanceof YITH_WCBK_Product_Booking ) {
if ( $product->has_resources() ) {
$resources = $product->get_resources(); // array of YITH_WCBK_Product_Resource
foreach ( $resources as $resource ) {
$resource_id = $resource->get_id();
$resource_name = $resource->get_name();
echo "<strong>Resource:</strong> $resource_name (ID: $resource_id)<br>";
// Check availability for next 7 days
$start_date = strtotime( 'today' );
$end_date = strtotime( '+7 days' );
for ( $date = $start_date; $date <= $end_date; $date += DAY_IN_SECONDS ) {
$available = YITH_WCBK_Availability::is_bookable(
$product_id,
[
'check_in_date' => $date,
'check_out_date' => $date + DAY_IN_SECONDS,
'resource_id' => $resource_id
]
);
if ( $available ) {
echo ' - Available on: ' . date( 'Y-m-d', $date ) . '<br>';
}
}
echo '<hr>';
}
} else {
echo 'No resources attached to this booking product.';
}
} else {
echo 'Invalid booking product or YITH plugin not active.';
}
—
### 🔍 What This Does:
– Checks if the product is an instance of `YITH_WCBK_Product_Booking`.
– Gets resources via `get_resources()`.
– Loops through 7 days to find availability using `YITH_WCBK_Availability::is_bookable()`.
—
### ✅ Output (example):
Resource: Room A
- Available on: 2025-07-31
- Available on: 2025-08-01
...
—
Would you like this to return structured **JSON** instead of `echo` output?
Even bother are valid
– The product is **valid and has resources**
– The **YITH Booking Premium plugin is active**
– But you're still seeing **`Invalid booking product or YITH plugin not active`**
Let’s **debug and fix this cleanly step-by-step**.
—
### ✅ Step-by-Step Diagnostic & Fix
#### ✅ 1. **Check the Actual Class of the Product**
Before checking for `YITH_WCBK_Product_Booking`, output the actual class returned by `wc_get_product()`:
$product_id = 123; // Replace with your product ID
$product = wc_get_product($product_id);
echo 'Class: ' . get_class($product);
**Expected Output:**
If the booking plugin is working correctly, you should see something like:
Class: YITH_WCBK_Product_Booking
If you get:
Class: WC_Product_Simple
Then your booking product is being returned as a **simple product**, which means YITH is **not overriding the product class correctly** — this happens when:
– Booking product type is not set correctly
– Custom product is loaded without YITH context (e.g. early during init)
—
#### ✅ 2. **Force YITH Booking Product Class**
Instead of relying on `wc_get_product()`, use the Booking Factory directly:
if ( class_exists( 'YITH_WCBK_Product_Booking' ) && class_exists( 'YITH_WCBK_Booking_Product_Factory' ) ) {
$booking_factory = new YITH_WCBK_Booking_Product_Factory();
$product = $booking_factory->get_booking_product( $product_id );
if ( $product && $product instanceof YITH_WCBK_Product_Booking ) {
$resources = $product->get_resources();
foreach ( $resources as $resource ) {
$resource_id = $resource->get_id();
$resource_name = $resource->get_name();
echo "<strong>Resource:</strong> $resource_name (ID: $resource_id)<br>";
$start_date = strtotime('today');
$end_date = strtotime('+7 days');
for ($date = $start_date; $date <= $end_date; $date += DAY_IN_SECONDS) {
$available = YITH_WCBK_Availability::is_bookable($product_id, [
'check_in_date' => $date,
'check_out_date' => $date + DAY_IN_SECONDS,
'resource_id' => $resource_id
]);
if ($available) {
echo ' - Available on: ' . date('Y-m-d', $date) . '<br>';
}
}
echo '<hr>';
}
} else {
echo 'Booking product not found or invalid.';
}
} else {
echo 'YITH Booking plugin classes not loaded.';
}
—
### 🔁 Summary
| 🔍 Problem | ✅ Fix |
|———–|——–|
| `wc_get_product()` returning non-booking class | Use `YITH_WCBK_Booking_Product_Factory` |
| `yith_get_booking_product()` undefined | It's not global — use factory class directly |
| Plugin active but object incorrect | Use correct loader timing (after `init` hook is safest) |
—
### ✅ Bonus Tip
Always run this code after plugins have initialized (use `init` or `woocommerce_loaded` hooks):
add_action('init', function() {
// Your code here
});
—
Let me know your product ID and I can show
That means you already have access to the global product object (`$product`) and don’t need to fetch it again via `wc_get_product()`.
—
### ✅ Safe & Working Code (From Product Detail Page)
global $product;
if ( class_exists( 'YITH_WCBK_Product_Booking' ) && $product instanceof YITH_WCBK_Product_Booking ) {
if ( $product->has_resources() ) {
$resources = $product->get_resources(); // Array of YITH_WCBK_Product_Resource
foreach ( $resources as $resource ) {
$resource_id = $resource->get_id();
$resource_name = $resource->get_name();
echo "<strong>Resource:</strong> $resource_name (ID: $resource_id)<br>";
// Loop next 7 days for availability
$start_date = strtotime( 'today' );
$end_date = strtotime( '+7 days' );
for ( $date = $start_date; $date <= $end_date; $date += DAY_IN_SECONDS ) {
$available = YITH_WCBK_Availability::is_bookable(
$product->get_id(),
[
'check_in_date' => $date,
'check_out_date' => $date + DAY_IN_SECONDS,
'resource_id' => $resource_id
]
);
if ( $available ) {
echo ' - Available on: ' . date( 'Y-m-d', $date ) . '<br>';
}
}
echo '<hr>';
}
} else {
echo 'No resources attached.';
}
} else {
echo 'This is not a valid booking product.';
}
—
### 🔍 Why This Works:
– You're already on the product page → `$product` is set.
– `YITH_WCBK_Product_Booking` extends `WC_Product`, so `instanceof` ensures it's a booking product.
– `get_resources()` gives you the actual resource objects (not just IDs).
– You loop through and check dates using `YITH_WCBK_Availability::is_bookable()`.
—
### 🧪 Optional: Debug Class
You can test what class the product is using:
echo 'Product Class: ' . get_class($product);
If it prints:
WC_Product_Simple
then something is wrong — either:
– The product type is **not set to "Booking"**
– The booking plugin didn’t override the class
– You're accessing too early in the template (before full setup)
In that case, force the class via:
$product_id = $product->get_id();
$factory = new YITH_WCBK_Booking_Product_Factory();
$product = $factory->get_booking_product($product_id);
—
Would you like this availability data as **JSON** for AJAX usage or calendar integration?
foreach ($resources_data as $resource_data) {
$resource = $resource_data->get_resource();
if ($resource) {
$pricing = $resource_data->get_pricing_html();
if (!!$pricing) {
$pricing = '+ ' . $pricing;
}
$description = apply_filters('yith_wcbk_resources_selector_resource_description', $pricing, $resource, $resource_data, $product);
$strek_avail[$resource->get_id()] = array(
'label' => $resource->get_name(),
'image_id' => $resource->get_image_id(),
'description' => $description,
);
if ($resource->get_image_id()) {
$use_images = true;
}
}
}
adjust in above code
$resources_data = $product->get_resources_data();
This returns an array of `YITH_WCBK_Product_Resource_Data` objects — each representing a **resource attached to the product**, including its dynamic pricing, availability, etc.
You’re already extracting `label`, `image_id`, `description`. Now let’s **add available dates per resource** (e.g., for the next 7 days).
—
### ✅ Modified Code: Add `available_dates` to Each Resource
$strek_avail = array();
$use_images = false;
// Loop each resource attached to product
foreach ($product->get_resources_data() as $resource_data) {
$resource = $resource_data->get_resource();
if ($resource) {
$pricing = $resource_data->get_pricing_html();
if (!!$pricing) {
$pricing = '+ ' . $pricing;
}
$description = apply_filters(
'yith_wcbk_resources_selector_resource_description',
$pricing,
$resource,
$resource_data,
$product
);
// Calculate available dates for this resource
$available_dates = [];
$start_date = strtotime('today');
$end_date = strtotime('+7 days');
for ($date = $start_date; $date <= $end_date; $date += DAY_IN_SECONDS) {
$is_available = YITH_WCBK_Availability::is_bookable($product->get_id(), [
'check_in_date' => $date,
'check_out_date' => $date + DAY_IN_SECONDS,
'resource_id' => $resource->get_id()
]);
if ($is_available) {
$available_dates[] = date('Y-m-d', $date);
}
}
// Store data per resource
$strek_avail[$resource->get_id()] = array(
'label' => $resource->get_name(),
'image_id' => $resource->get_image_id(),
'description' => $description,
'available_dates' => $available_dates
);
if ($resource->get_image_id()) {
$use_images = true;
}
}
}
—
### 🧪 Example Output (`$strek_avail`):
[
342 => [
'label' => 'Room A',
'image_id' => 1299,
'description' => '+ €10',
'available_dates' => [
'2025-07-30',
'2025-07-31',
'2025-08-02',
...
]
],
...
]
—
### ✅ Optional: Return as JSON (e.g., for AJAX)
wp_send_json_success($strek_avail);
Or for debugging:
echo '<pre>';
print_r($strek_avail);
echo '</pre>';
—
Let me know if you'd like to:
– Filter by **partial availability**
– Use **hourly booking slots**
– Or fetch **prices per date** too
Start date and end date
However, **YITH Booking Premium does not provide a direct method** like `get_all_available_dates()` — availability is dynamically computed for specific date ranges. So, if you want to **retrieve available dates without defining a range**, you have 2 practical options:
—
### ✅ Option 1: Use Booking Calendar's precomputed availability via `YITH_WCBK_Product_Booking::get_disabled_days()`
This method returns **disabled/unavailable days**, so you can reverse them to get available ones.
$disabled_days = $product->get_disabled_days(); // e.g., [0 => true, 1 => true, 6 => true]
But this returns only **weekdays (0 = Sunday to 6 = Saturday)** that are globally disabled — not precise per resource.
So not suitable if you're using **resources with their own availability**.
—
### ✅ Option 2: Dynamically scan upcoming N days — but not hardcoded inside your code
If you **don't want to hardcode** start/end in PHP, you can:
#### ✅ A. Let frontend or shortcode or filter decide the date range dynamically
E.g., next 30 days:
$days = apply_filters('yith_booking_resource_availability_days', 30);
Then:
$start_date = strtotime('today');
$end_date = strtotime("+{$days} days");
This way, you don’t hardcode but use a filter-based dynamic control.
—
### ✅ Option 3: Use `YITH_WCBK_Product_Resource_Data::is_available()` directly (no date range logic)
YITH does not provide all dates available without passing dates because **availability depends on dynamic rules, manual rules, holidays, and resource capacity**.
But here's a minimal version that avoids passing dates at all:
foreach ($product->get_resources_data() as $resource_data) {
$resource = $resource_data->get_resource();
if ($resource) {
$is_resource_available = $resource_data->is_available(); // No date passed
$strek_avail[$resource->get_id()] = array(
'label' => $resource->get_name(),
'available_now' => $is_resource_available, // true/false
);
}
}
💡 This only tells you if the resource is **currently available** under general booking rules, not when in future.
—
### ✅ Final Recommendation
Since YITH’s system is **date-based and dynamic**, the cleanest way is:
$default_range = apply_filters('yith_booking_default_availability_range', 30);
$start = strtotime('today');
$end = strtotime("+{$default_range} days");
// Then scan as in previous example
If you truly want **"get all future available dates"**, then you must:
– Define a range programmatically (e.g., next 90 days)
– Or hook a filter and expose it to user settings
—
Would you like me to write a reusable function with optional filter for the range?
– Works with `$product->get_resources_data()`
– Calculates **available dates per resource**
– Does **not hardcode** the date range — uses a filter so you can override the number of days externally
– Returns everything in a well-structured `$strek_avail` array
—
### ✅ Full Code – Get Resource Availability Without Hardcoded Dates
$strek_avail = array();
$use_images = false;
if ( ! isset( $product ) || ! method_exists( $product, 'get_resources_data' ) ) {
return;
}
// Use filter to set date range dynamically (default: 30 days)
$days_to_check = apply_filters( 'yith_booking_resource_availability_days', 30 );
$start_date = strtotime('today');
$end_date = strtotime("+{$days_to_check} days");
foreach ( $product->get_resources_data() as $resource_data ) {
$resource = $resource_data->get_resource();
if ( $resource ) {
$pricing = $resource_data->get_pricing_html();
if ( ! empty( $pricing ) ) {
$pricing = '+ ' . $pricing;
}
$description = apply_filters(
'yith_wcbk_resources_selector_resource_description',
$pricing,
$resource,
$resource_data,
$product
);
// Collect available dates
$available_dates = [];
for ( $date = $start_date; $date <= $end_date; $date += DAY_IN_SECONDS ) {
$is_available = YITH_WCBK_Availability::is_bookable( $product->get_id(), [
'check_in_date' => $date,
'check_out_date' => $date + DAY_IN_SECONDS,
'resource_id' => $resource->get_id(),
] );
if ( $is_available ) {
$available_dates[] = date( 'Y-m-d', $date );
}
}
// Build final array
$strek_avail[ $resource->get_id() ] = array(
'label' => $resource->get_name(),
'image_id' => $resource->get_image_id(),
'description' => $description,
'available_dates' => $available_dates,
);
if ( $resource->get_image_id() ) {
$use_images = true;
}
}
}
—
### 🧪 Output Example of `$strek_avail`:
Array
(
[1034] => Array
(
[label] => "Deluxe Room"
[image_id] => 531
[description] => "+ €25"
[available_dates] => Array
(
[0] => "2025-07-30"
[1] => "2025-07-31"
[2] => "2025-08-01"
...
)
)
)
—
### 🛠 Bonus: Change the Range Without Touching Code
Add this in `functions.php` to override the number of days to scan:
add_filter( 'yith_booking_resource_availability_days', function() {
return 60; // Check for next 60 days
});
—
Let me know if you want this returned as JSON or used in a shortcode/AJAX.
—
### ✅ Correct Way: Instantiate the Availability Class First
Update this part:
$is_available = YITH_WCBK_Availability::is_bookable(...)
🔄 With this:
$availability = new YITH_WCBK_Availability();
$is_available = $availability->is_bookable(
$product->get_id(),
[
'check_in_date' => $date,
'check_out_date' => $date + DAY_IN_SECONDS,
'resource_id' => $resource->get_id(),
]
);
—
### ✅ Final Full Working Code (Fixed + Cleaned Up)
$strek_avail = array();
$use_images = false;
if ( ! isset( $product ) || ! method_exists( $product, 'get_resources_data' ) ) {
return;
}
// Dynamically set date range via filter (default: 30 days)
$days_to_check = apply_filters( 'yith_booking_resource_availability_days', 30 );
$start_date = strtotime('today');
$end_date = strtotime("+{$days_to_check} days");
// Instantiate the non-static class
$availability = new YITH_WCBK_Availability();
foreach ( $product->get_resources_data() as $resource_data ) {
$resource = $resource_data->get_resource();
if ( $resource ) {
$pricing = $resource_data->get_pricing_html();
if ( ! empty( $pricing ) ) {
$pricing = '+ ' . $pricing;
}
$description = apply_filters(
'yith_wcbk_resources_selector_resource_description',
$pricing,
$resource,
$resource_data,
$product
);
// Collect available dates
$available_dates = [];
for ( $date = $start_date; $date <= $end_date; $date += DAY_IN_SECONDS ) {
$is_available = $availability->is_bookable(
$product->get_id(),
[
'check_in_date' => $date,
'check_out_date' => $date + DAY_IN_SECONDS,
'resource_id' => $resource->get_id(),
]
);
if ( $is_available ) {
$available_dates[] = date( 'Y-m-d', $date );
}
}
// Build resource info
$strek_avail[ $resource->get_id() ] = array(
'label' => $resource->get_name(),
'image_id' => $resource->get_image_id(),
'description' => $description,
'available_dates' => $available_dates,
);
if ( $resource->get_image_id() ) {
$use_images = true;
}
}
}
—
### ✅ Optional Debug Output
echo '<pre>';
print_r($strek_avail);
echo '</pre>';
—
Let me know if you'd like this wrapped as a function, returned as JSON for AJAX, or displayed in frontend HTML.
<style>
/* Toggle Button */
.toggle-btn {
transition: background 0.2s, color 0.2s;
}
.toggle-btn.active {
background: #00c48c !important;
color: #fff !important;
}
/* Calendar Containers */
.weekly-calendar,
.monthly-calendar {
margin: 30px 0;
font-family: Arial, sans-serif;
}
/* Table Styling */
.weekly-calendar table,
.monthly-calendar table {
width: 100%;
border-collapse: collapse;
text-align: center;
min-width: 220px;
margin-bottom: 0;
}
/* Table Headers and Cells */
.weekly-calendar th,
.weekly-calendar td,
.monthly-calendar th,
.monthly-calendar td {
border: none;
}
.weekly-calendar th {
text-align: center;
vertical-align: middle;
font-size: 12px;
}
.weekly-calendar td {
font-size: 14px !important;
font-weight: 600 !important;
}
.weekly-calendar th small {
font-size: 10px;
color: #666;
display: block;
margin-top: 2px;
}
.weekly-calendar th,
.monthly-calendar th {
}
.yith-wcbk-booking-form button#prev-week-btn ,.yith-wcbk-booking-form button#next-week-btn{
background: none;
border:none;
cursor: pointer;
}
.yith-wcbk-form-section-dates-wrapper .toggle-btn {
padding: 10px !important;
text-align: center;
font-size: 14px;
font-weight: 600;
}
.weekly-calendar .selected,
.monthly-calendar .selected {
color: #fff;
}
/* Monthly Calendar Layout */
.monthly-calendar {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.prev_month ,.next_month {
background: none;
border: none;
cursor: pointer;
}
.monthly-calendar caption {
font-weight: bold;
margin-bottom: 5px;
}
.monthly-calendar .day-checkbox {
display: none;
}
/* 2. Style the label as a box */
label[for^="day-"] {
display: inline-block;
text-align: center;
font-weight: bold;
position: relative;
cursor: pointer;
}
/* 3. Show tick icon when checked */
.day-checkbox:checked + label[for^="day-"]::after {
content: "✔";
color: green;
font-size: 19px;
position: absolute;
top: -5px;
right: 0px;
background: #ccffcc;
width: 100%;
}
.monthly-calendar td:has(input.unavailable) {
background-color: #ffcccc !important;
border-radius: 5px;
padding: 10px !important;
}
.monthly-calendar td.selected{
background: #00c48c !important;
border-radius: 5px;
padding: 10px !important;
}
.monthly-calendar td:not(:first-child) {
background: #ccffcc;
border-radius: 5px;
}
/* Scroll Container */
.monthly-calendar-scroll {
width: 100%;
overflow-y: auto;
}
.monthly-calendar-scroll.scrollable {
max-height: 350px;
}
/* Calendar Block */
.strek-calendar-block {
margin-bottom: 24px;
background: #fff;
padding: 8px 0 16px 0;
}
.weekly-calendar .strek-calendar-block:last-child {
border-bottom: none;
}
.weekly-calendar td:has(input.unavailable) {
background-color: #ffcccc !important;
}
.weekly-calendar td.selected{
background: #00c48c !important;
}
/* Custom Checkbox */
.weekly-calendar .strek-checkbox {
appearance: none;
-webkit-appearance: none;
width: 20px;
height: 20px;
border: 2px solid #ccc;
border-radius: 4px;
outline: none;
cursor: pointer;
position: relative;
vertical-align: middle; /* default: available (green) */
transition: background 0.2s;
}
.weekly-calendar .strek-checkbox:checked::after {
content: "✔";
display: inline-block;
color: #ffffff;
font-size: 19px;
position: relative;
left: 2px;
top: 0px;
}
.weekly-calendar .week-checkbox.strek-checkbox.unavailable::after {
content: "✖"; /* Unicode cross */
color: red;
font-size: 19px;
position: absolute;
top: 0;
left: 2px;
}
.weekly-calendar .strek-checkbox.unavailable {
background: #ffcccc !important; /* unavailable (red) */
border-color: #ff8888;
border:none;
}
.weekly-calendar .strek-checkbox:disabled {
cursor: not-allowed;
opacity: 0.7;
}
.weekly-calendar .strek-checkbox:checked {
background: #00c48c !important;
border-color: #00c48c;
border:none;
}
/* Weekly calendar specific styles */
.weekly-calendar td {
text-align: center;
vertical-align: middle;
position: relative;
padding: 0px !important;
}
.weekly-calendar td:not(:first-child){
background: #ccffcc;
border-radius: 5px;
}
.weekly-calendar tr{
border:none !important;
}
td small {
font-size: 10px;
line-height: 1.2;
margin-top: 4px;
display: none;
}
.woocommerce div.product form.cart table{
border-collapse: separate;
border-spacing: 12px;
}
</style>
<script>
let displayWeekly='<?php echo $displayWeekly; ?>';
let displayPerday='<?php echo $displayPerday; ?>';
const strekAvail = <?php echo json_encode($strek_avail); ?>;
const strekMonthIdx = {};
Object.keys(strekAvail).forEach(stekId => {
strekMonthIdx[stekId] = 0; // Start at first month for each strek
});
const allowedDays = <?php echo json_encode($product->get_allowed_start_days()); ?>; // 1=Monday, 5=Friday, etc.
console.log(strekAvail);
document.addEventListener('DOMContentLoaded', function() {
const weekList = <?php echo json_encode($week_list); ?>;
const weeksToShow = <?php echo $weeks_to_show; ?>;
const loadedMonths = <?php echo json_encode($loaded_months); ?>;
let weekOffset = 0;
let currentMonthIdx = 0;
const notAvailableDates = <?php echo json_encode($not_available_dates); ?>;
// Helper function to check if any day in a week is unavailable
const weekHasUnavailableDates = (weekDates, notAvailableDates) => {
for (let i = 0; i < weekDates.length; i++) {
if (notAvailableDates.includes(weekDates[i])) {
return true;
}
}
return false;
};
// WEEKLY CALENDAR
const renderCalendar = () => {
let html = `<table><thead><tr><th>Stek</th>`;
// Table headers (weeks)
for (let i = 0; i < weeksToShow; i++) {
const weekIdx = weekOffset + i;
if (weekIdx >= weekList.length) break;
const w = weekList[weekIdx];
// Find allowed weekday (e.g. Friday) in this week
let allowedDate = null;
for (let d = 0; d < w.dates.length; d++) {
const dateObj = new Date(w.dates[d]);
// getDay: 0=Sunday, 1=Monday, ..., 5=Friday
// allowedDays uses 1=Monday, 7=Sunday (ISO)
const isoDay = dateObj.getDay() === 0 ? 7 : dateObj.getDay();
if (allowedDays.includes(isoDay)) {
allowedDate = w.dates[d];
break;
}
}
let thLabel = `Week ${w.week}`;
if (allowedDate) {
const dateObj = new Date(allowedDate);
thLabel += `<br><small>${dateObj.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' })}</small>`;
}
html += `<th>${thLabel}</th>`;
}
html += `</tr></thead><tbody>`;
// For each stek, create a row
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
html += `<tr><td>${stekData.label}</td>`;
for (let i = 0; i < weeksToShow; i++) {
const weekIdx = weekOffset + i;
if (weekIdx >= weekList.length) break;
const w = weekList[weekIdx];
// Find allowed weekday (e.g. Friday) in this week
let allowedDate = null;
for (let d = 0; d < w.dates.length; d++) {
const dateObj = new Date(w.dates[d]);
const isoDay = dateObj.getDay() === 0 ? 7 : dateObj.getDay();
if (allowedDays.includes(isoDay)) {
allowedDate = w.dates[d];
break;
}
}
let isUnavailable = true;
if (allowedDate && !notAvailableDates.includes(allowedDate)) {
isUnavailable = false;
}Not available : from 02/08/2025 to 09/08/2025
const unavailableClass = isUnavailable ? 'unavailable' : '';
html += `<td style="padding: 20px 10px;">
<input type="checkbox" class="week-checkbox strek-checkbox ${unavailableClass}" id="week-check-${stekId}-${weekIdx}"
data-stek="${stekId}" data-week="${w.week}" data-year="${w.year}" data-date="${allowedDate ? allowedDate : ''}" ${isUnavailable ? 'disabled' : ''}>
${allowedDate ? `<br><small>${new Date(allowedDate).toLocaleDateString('nl-NL', { weekday: 'short', day: '2-digit', month: '2-digit' })}</small>` : '<br><small>n.v.t.</small>'}
</td>`;
}
html += `</tr>`;
});
html += `</tbody></table>`;
document.getElementById('calendar-table-wrapper').innerHTML = html;
// Update navigation label
const navLabel = document.getElementById('week-navigation-label');
if (navLabel && weekList.length > 0) {
const firstWeek = weekList[weekOffset];
const lastWeek = weekList[Math.min(weekOffset + weeksToShow - 1, weekList.length - 1)];
if (firstWeek && lastWeek) {
const firstDate = new Date(firstWeek.start);
const lastDate = new Date(lastWeek.start);
lastDate.setDate(lastDate.getDate() + 6);
navLabel.textContent = `Weeks ${firstWeek.week} - ${lastWeek.week} (${firstDate.toLocaleDateString('nl-NL')} - ${lastDate.toLocaleDateString('nl-NL')})`;
}
}
// Selection highlight for checkboxes and event handlers
document.querySelectorAll('.weekly-calendar .week-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', () => {
const td = checkbox.closest('td');
if (checkbox.checked) {
// Uncheck all other week checkboxes across all streks
document.querySelectorAll('.weekly-calendar .week-checkbox').forEach(cb => {
if (cb !== checkbox) {
cb.checked = false;
cb.closest('td').classList.remove('selected');
}
});
td.classList.add('selected');
// Update the booking form fields
const strekId = checkbox.getAttribute('data-stek');
const productId = "<?php echo $product->get_id(); ?>";
const selectedDate = checkbox.getAttribute('data-date');
// Set the check-in (start date) field
const startDateInput = document.getElementById(`yith-wcbk-booking-start-date-${productId}`);
if (startDateInput) {
startDateInput.value = selectedDate;
startDateInput.dispatchEvent(new Event('input', { bubbles: true }));
startDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(startDateInput).datepicker) {
jQuery(startDateInput).datepicker('setDate', selectedDate);
}
}
// Set the check-out (end date) field to 7 days after check-in
const endDateInput = document.getElementById(`yith-wcbk-booking-end-date-${productId}`);
if (endDateInput) {
const dateObj = new Date(selectedDate);
dateObj.setDate(dateObj.getDate() + 7); // 7 days (week)
const yyyy = dateObj.getFullYear();
const mm = String(dateObj.getMonth() + 1).padStart(2, '0');
const dd = String(dateObj.getDate()).padStart(2, '0');
const endDate = `${yyyy}-${mm}-${dd}`;
endDateInput.value = endDate;
endDateInput.dispatchEvent(new Event('input', { bubbles: true }));
endDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(endDateInput).datepicker) {
jQuery(endDateInput).datepicker('setDate', endDate);
}
}
// Update strek dropdown
const select = document.querySelector(`#yith-wcbk-booking-resources-${productId} select.yith-wcbk-selector__field`);
if (select) {
select.value = strekId;
const event = new Event('change', { bubbles: true });
select.dispatchEvent(event);
const customItem = document.querySelector(`#yith-wcbk-booking-resources-${productId} .yith-wcbk-selector__item[data-key="${strekId}"]`);
if (customItem) {
customItem.click();
}
}
} else {
td.classList.remove('selected');
}
});
});
};
document.getElementById('prev-week-btn')?.remove();
document.getElementById('next-week-btn')?.remove();
const navDiv = document.createElement('div');
navDiv.style.display = "flex";
navDiv.style.justifyContent = "center";
navDiv.style.marginBottom = "10px";
navDiv.innerHTML = `
<button type="button" id="prev-week-btn" style="display:<?php echo $displayWeekly; ?>;"> <img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/left-chevron-svgrepo-com.svg" alt="Previous" width="25" height="25"> </button>
<div style="flex: 1; text-align: center; font-weight: bold;display:<?php echo $displayWeekly; ?>;" id="week-navigation-label">Select a week</div>
<button type="button" id="next-week-btn" style="display:<?php echo $displayWeekly; ?>;"> <img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/right-chevron-svgrepo-com.svg" alt="Previous" width="25" height="25"> </button>
`;
document.getElementById('calendar-table-wrapper').before(navDiv);
document.getElementById('prev-week-btn').addEventListener('click', () => {
if (weekOffset - weeksToShow >= 0) {
weekOffset -= weeksToShow;
} else {
weekOffset = 0;
}
renderCalendar();
});
document.getElementById('next-week-btn').addEventListener('click', () => {
if (weekOffset + weeksToShow < weekList.length) {
weekOffset += weeksToShow;
renderCalendar();
}
});
// MONTHLY CALENDAR (one month at a time)
const renderMonthlyCalendar = () => {
if (loadedMonths.length === 0) {
document.getElementById('monthly-calendar-scroll').innerHTML = '<p>No months available.</p>';
document.getElementById('monthly-calendar-controls').style.display = 'none';
return;
}
document.getElementById('monthly-calendar-controls').style.display = 'none'; // Hide global controls
let html = '';
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
html += renderStrekMonthBlock(stekId, stekData);
});
document.getElementById('monthly-calendar-scroll').innerHTML = html;
// Add event listeners for each strek's navigation
Object.keys(strekAvail).forEach(stekId => {
document.getElementById(`prev-month-btn-${stekId}`).addEventListener('click', () => {
if (strekMonthIdx[stekId] > 0) {
strekMonthIdx[stekId]--;
renderMonthlyCalendar();
}
});
document.getElementById(`next-month-btn-${stekId}`).addEventListener('click', () => {
if (strekMonthIdx[stekId] < loadedMonths.length - 1) {
strekMonthIdx[stekId]++;
renderMonthlyCalendar();
}
});
});
// Add scrollable class if needed
const strekCount = Object.keys(strekAvail).length;
const scrollDiv = document.getElementById('monthly-calendar-scroll');
if (strekCount > 2) {
scrollDiv.classList.add('scrollable');
} else {
scrollDiv.classList.remove('scrollable');
}
};
const renderStrekMonthBlock = (stekId, stekData) => {
const idx = strekMonthIdx[stekId];
const [year, month] = loadedMonths[idx].split('-').map(Number);
// Get first and last day of month
const firstDay = new Date(year, month - 1, 1);
const lastDay = new Date(year, month, 0);
const daysInMonth = lastDay.getDate();
let html = `<div class="strek-calendar-block">
<div style="font-weight:bold; margin-bottom:20px;">${stekData.label}</div>
<div style="text-align:center; margin-bottom:10px; display:flex; align-items: center; justify-content: space-between;">
<button type="button" id="prev-month-btn-${stekId}" class="prev_month"><img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/left-chevron-svgrepo-com.svg" alt="Next" width="25" height="25"> </button>
<span style="font-weight:bold; margin:0 10px;">${firstDay.toLocaleString('default', { month: 'long', year: 'numeric' })}</span>
<button type="button" id="next-month-btn-${stekId}" class="next_month"><img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/right-chevron-svgrepo-com.svg" alt="Next" width="25" height="25"></button>
</div>
<table>
<thead>
<tr>
<th>Mo</th><th>Tu</th><th>We</th><th>Th</th><th>Fr</th><th>Sa</th><th>Su</th>
</tr>
</thead>
<tbody>
<tr>`;
// Calculate the day of week for the 1st (Monday=0)
let dayOfWeek = (firstDay.getDay() + 6) % 7;
for (let i = 0; i < dayOfWeek; i++) html += '<td></td>';
for (let day = 1; day <= daysInMonth; day++) {
if ((dayOfWeek % 7 === 0) && day !== 1) html += '</tr><tr>';
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
const isUnavailable = notAvailableDates.includes(dateStr);
const unavailableClass = isUnavailable ? 'unavailable' : '';
html += `<td>
<input type="checkbox" class="day-checkbox strek-checkbox ${unavailableClass}" id="day-${stekId}-${year}-${month}-${day}" data-date="${dateStr}" data-stek="${stekId}" ${isUnavailable ? 'disabled' : ''}>
<label for="day-${stekId}-${year}-${month}-${day}">${day}</label>
</td>`;
dayOfWeek++;
}
while (dayOfWeek % 7 !== 0) { html += '<td></td>'; dayOfWeek++; }
html += '</tr></tbody></table></div>';
return html;
};
const renderSingleMonth = (year, month) => {
// For each strek, render a classic calendar
let html = '';
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
// Get first and last day of month
const firstDay = new Date(year, month - 1, 1);
const lastDay = new Date(year, month, 0);
const daysInMonth = lastDay.getDate();
html += `<div class="strek-calendar-block">
<div style="font-weight:bold; margin-bottom:4px;">${stekData.label}</div>
<table>
<caption>${firstDay.toLocaleString('default', { month: 'long' })} ${year}</caption>
<thead>
<tr>
<th>Mo</th><th>Tu</th><th>We</th><th>Th</th><th>Fr</th><th>Sa</th><th>Su</th>
</tr>
</thead>
<tbody>
<tr>`;
// Calculate the day of week for the 1st (Monday=0)
let dayOfWeek = (firstDay.getDay() + 6) % 7;
for (let i = 0; i < dayOfWeek; i++) html += '<td></td>';
for (let day = 1; day <= daysInMonth; day++) {
if ((dayOfWeek % 7 === 0) && day !== 1) html += '</tr><tr>';
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
html += `<td>
<input type="checkbox" class="day-checkbox strek-checkbox" id="day-${stekId}-${year}-${month}-${day}" data-date="${dateStr}" data-stek="${stekId}">
<label for="day-${stekId}-${year}-${month}-${day}">${day}</label>
</td>`;
dayOfWeek++;
}
while (dayOfWeek % 7 !== 0) { html += '<td></td>'; dayOfWeek++; }
html += '</tr></tbody></table></div>';
});
return html;
};
// TOGGLE LOGIC
document.getElementById('toggle-week').addEventListener('click', function() {
document.getElementById('toggle-week').classList.add('active');
document.getElementById('toggle-day').classList.remove('active');
document.getElementById('calendar-table-wrapper').style.display = '';
document.getElementById('monthly-calendar-wrapper').style.display = 'none';
document.getElementById('monthly-calendar-controls').style.display = 'none';
navDiv.style.display = '';
renderCalendar();
});
document.getElementById('toggle-day').addEventListener('click', function() {
document.getElementById('toggle-day').classList.add('active');
document.getElementById('toggle-week').classList.remove('active');
document.getElementById('calendar-table-wrapper').style.display = 'none';
document.getElementById('monthly-calendar-wrapper').style.display = '';
document.getElementById('monthly-calendar-controls').style.display = '';
navDiv.style.display = 'none';
renderMonthlyCalendar();
});
// Initial render
if(displayWeekly=='block'){
renderCalendar();
}else{
renderMonthlyCalendar();
}
document.getElementById('prev-month-btn').addEventListener('click', () => {
if (currentMonthIdx > 0) {
currentMonthIdx--;
renderMonthlyCalendar();
}
});
document.getElementById('next-month-btn').addEventListener('click', () => {
if (currentMonthIdx < loadedMonths.length - 1) {
currentMonthIdx++;
renderMonthlyCalendar();
}
});
document.addEventListener('change', function(e) {
if (
e.target.classList.contains('day-checkbox') &&
e.target.checked &&
!e.target.disabled
) {
// Uncheck all other day-checkboxes
document.querySelectorAll('.day-checkbox').forEach(cb => {
if (cb !== e.target) cb.checked = false;
});
const strekId = e.target.getAttribute('data-stek');
const productId = "<?php echo $product->get_id(); ?>";
const selectedDate = e.target.getAttribute('data-date');
// Set the check-in (start date) field and fire change event
const startDateInput = document.getElementById(`yith-wcbk-booking-start-date-${productId}`);
startDateInput.value = selectedDate;
// Fire both input and change events
startDateInput.dispatchEvent(new Event('input', { bubbles: true }));
startDateInput.dispatchEvent(new Event('change', { bubbles: true }));
// If a jQuery datepicker is attached, update it as well
if (typeof jQuery !== 'undefined' && jQuery(startDateInput).datepicker) {
jQuery(startDateInput).datepicker('setDate', selectedDate);
}
// Set the check-out (end date) field to one day after check-in
const endDateInput = document.getElementById(`yith-wcbk-booking-end-date-${productId}`);
if (endDateInput) {
// Calculate next day
const dateObj = new Date(selectedDate);
dateObj.setDate(dateObj.getDate() + 1);
// Format as yyyy-mm-dd
const yyyy = dateObj.getFullYear();
const mm = String(dateObj.getMonth() + 1).padStart(2, '0');
const dd = String(dateObj.getDate()).padStart(2, '0');
const nextDay = `${yyyy}-${mm}-${dd}`;
endDateInput.value = nextDay;
endDateInput.dispatchEvent(new Event('input', { bubbles: true }));
endDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(endDateInput).datepicker) {
jQuery(endDateInput).datepicker('setDate', nextDay);
}
}
// Strek dropdown logic (as before)
const select = document.querySelector(`#yith-wcbk-booking-resources-${productId} select.yith-wcbk-selector__field`);
if (select) {
select.value = strekId;
const event = new Event('change', { bubbles: true });
select.dispatchEvent(event);
const customItem = document.querySelector(`#yith-wcbk-booking-resources-${productId} .yith-wcbk-selector__item[data-key="${strekId}"]`);
if (customItem) {
customItem.click();
}
}
}
});
});
</script>
Now I want avialble date for each strek resources and based on it put weekly calender checkbox green for available and red for non avialble
—
### ✅ GOAL:
Update the `$strek_avail` PHP array to include available dates per resource so the frontend can color checkboxes accordingly using JavaScript.
—
### ✅ STEP 1: Modify `$strek_avail` to include `available_dates`
Add this in your PHP where `$resources_data` is built:
$availability = new YITH_WCBK_Availability();
$days_to_check = apply_filters('yith_booking_resource_availability_days', 90); // Scan next 90 days
$start_date = strtotime('today');
$end_date = strtotime("+{$days_to_check} days");
foreach ($resources_data as $resource_data) {
$resource = $resource_data->get_resource();
if ($resource) {
$pricing = $resource_data->get_pricing_html();
if (!!$pricing) {
$pricing = '+ ' . $pricing;
}
$description = apply_filters('yith_wcbk_resources_selector_resource_description', $pricing, $resource, $resource_data, $product);
// Collect available dates
$available_dates = [];
for ($date = $start_date; $date <= $end_date; $date += DAY_IN_SECONDS) {
$is_available = $availability->is_bookable($product->get_id(), [
'check_in_date' => $date,
'check_out_date' => $date + DAY_IN_SECONDS,
'resource_id' => $resource->get_id(),
]);
if ($is_available) {
$available_dates[] = date('Y-m-d', $date);
}
}
$strek_avail[$resource->get_id()] = array(
'label' => $resource->get_name(),
'image_id' => $resource->get_image_id(),
'description' => $description,
'available_dates' => $available_dates,
);
if ($resource->get_image_id()) {
$use_images = true;
}
}
}
—
### ✅ STEP 2: In JavaScript, Update Class Based on Resource Availability
In your “, inside `renderCalendar()`, update this part:
const isUnavailable = !allowedDate || !strekData.available_dates.includes(allowedDate);
const unavailableClass = isUnavailable ? 'unavailable' : '';
Also, mark the checkbox like:
<input type="checkbox" class="week-checkbox strek-checkbox ${unavailableClass}" ...>
✅ If `unavailableClass = 'unavailable'`, your red styling will apply via:
.weekly-calendar .strek-checkbox.unavailable {
background: #ffcccc !important;
}
—
### ✅ RESULT:
Each **checkbox** in the calendar:
– Turns **green** (selected ✅) when clicked and available.
– Shows as **red** if the date is unavailable for that resource.
—
Would you like me to:
– Output this as a single reusable function?
– Help optimize frontend rendering using cached data?
Let’s fix this properly and show **green/red checkboxes per strek and week** based on your `$strek_avail`.
—
### ✅ Corrected JavaScript `renderCalendar()` Snippet
Replace this block in your `renderCalendar()`:
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
html += `<tr><td>${stekData.label}</td>`;
for (let i = 0; i < weeksToShow; i++) {
const weekIdx = weekOffset + i;
if (weekIdx >= weekList.length) break;
const w = weekList[weekIdx];
// Find allowed weekday in this week
let allowedDate = null;
for (let d = 0; d < w.dates.length; d++) {
const dateObj = new Date(w.dates[d]);
const isoDay = dateObj.getDay() === 0 ? 7 : dateObj.getDay();
if (allowedDays.includes(isoDay)) {
allowedDate = w.dates[d];
break;
}
}
// ✅ Fix: Use `stekData.available_dates`
const isAvailable = allowedDate && stekData.available_dates.includes(allowedDate);
const unavailableClass = isAvailable ? '' : 'unavailable';
html += `<td style="padding: 20px 10px;">
<input type="checkbox" class="week-checkbox strek-checkbox ${unavailableClass}"
id="week-check-${stekId}-${weekIdx}"
data-stek="${stekId}"
data-week="${w.week}"
data-year="${w.year}"
data-date="${allowedDate ? allowedDate : ''}"
${isAvailable ? '' : 'disabled'}>
${allowedDate
? `<br><small>${new Date(allowedDate).toLocaleDateString('nl-NL', { weekday: 'short', day: '2-digit', month: '2-digit' })}</small>`
: '<br><small>n.v.t.</small>'}
</td>`;
}
html += `</tr>`;
});
—
### ✅ Summary of Fixes:
– `strekData` now comes from `forEach(([stekId, stekData]) => {…})` correctly.
– We now check availability via:
stekData.available_dates.includes(allowedDate)
– Red class: `.unavailable` is added only if that date is not available for the current `strek`.
—
### ✅ Styling Recap (already in your CSS):
.weekly-calendar .strek-checkbox.unavailable {
background: #ffcccc !important;
border-color: #ff8888;
}
—
Let me know if you want the same logic for **monthly calendar** too — I can update that section next.
> `Uncaught TypeError: Cannot read properties of undefined (reading 'includes')`
means this part of your JS:
stekData.available_dates.includes(allowedDate)
is failing because `stekData.available_dates` is `undefined`.
—
### ✅ Fix: Ensure `available_dates` is Always Present
#### ✅ 1. In PHP, fix `$strek_avail` structure:
Double-check that each `strek_avail` entry **always includes `available_dates` as an array**, even if empty:
$strek_avail[$resource->get_id()] = array(
'label' => $resource->get_name(),
'image_id' => $resource->get_image_id(),
'description' => $description,
'available_dates' => $available_dates ?? [], // always defined
);
> ✅ This ensures `available_dates` exists even if no dates are found.
—
#### ✅ 2. In JavaScript: Add a null check before `.includes`
Change this line:
const isAvailable = allowedDate && stekData.available_dates.includes(allowedDate);
🔁 To this safer version:
const isAvailable = allowedDate && Array.isArray(stekData.available_dates) && stekData.available_dates.includes(allowedDate);
This avoids the error if `available_dates` is missing or not an array.
—
### ✅ Optional Debug Tip
Add this to log what `stekData` looks like per row:
console.log('stekId:', stekId, 'stekData:', stekData);
—
### ✅ Summary
– ✅ PHP guarantees `available_dates` exists
– ✅ JS guards `.includes()` with `Array.isArray(…)`
– ✅ No more crashes + checkbox coloring works
Let me know if you want to highlight unavailable cells with ❌ too.
– ✅ Dates available for the current strek (resource) show **green checkboxes**
– ❌ Dates not available show **disabled red checkboxes**
—
### ✅ STEP 1: Ensure `available_dates` are included (already done)
From earlier, your `$strek_avail` array now includes:
'available_dates' => $available_dates ?? []
And passed to JavaScript via:
const strekAvail = <?php echo json_encode($strek_avail); ?>;
—
### ✅ STEP 2: Update `renderStrekMonthBlock()` JS function
In your `renderStrekMonthBlock`, update this block:
#### ❌ Old (unsafe):
const isUnavailable = notAvailableDates.includes(dateStr);
#### ✅ New (safe and per resource):
Replace with:
const availableDates = Array.isArray(stekData.available_dates) ? stekData.available_dates : [];
const isAvailable = availableDates.includes(dateStr);
const isUnavailable = !isAvailable;
Then use `isUnavailable` as before:
const unavailableClass = isUnavailable ? 'unavailable' : '';
const disabledAttr = isUnavailable ? 'disabled' : '';
✅ Apply to your “ checkbox output:
<input type="checkbox" class="day-checkbox strek-checkbox ${unavailableClass}"
id="day-${stekId}-${year}-${month}-${day}"
data-date="${dateStr}"
data-stek="${stekId}"
${disabledAttr}>
—
### ✅ Final Key Insert Inside `renderStrekMonthBlock`:
Below this line:
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
Add:
const availableDates = Array.isArray(stekData.available_dates) ? stekData.available_dates : [];
Then later inside the loop for each `day`, add:
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
const isAvailable = availableDates.includes(dateStr);
const unavailableClass = isAvailable ? '' : 'unavailable';
const disabledAttr = isAvailable ? '' : 'disabled';
—
### ✅ Result:
– ✅ Green checkboxes for available dates
– ❌ Red + disabled checkboxes for unavailable dates
– ✅ No JS errors (`includes` is safe)
Let me know if you'd like to:
– Automatically pre-select the first available date
– Add a tooltip on unavailable days
– Sync both calendars (week/month) selections
—
## ✅ GOAL
For **each strek (resource)** in the **monthly calendar**:
– ✅ Show **green checkbox** if the date is available
– ❌ Show **red disabled checkbox** if the date is not available
—
## ✅ STEP-BY-STEP IMPLEMENTATION
—
### 🔁 Step 1: Ensure `available_dates` is passed from PHP
You already have this PHP:
$strek_avail[$resource->get_id()] = array(
'label' => $resource->get_name(),
'image_id' => $resource->get_image_id(),
'description' => $description,
'available_dates' => $available_dates ?? [], // 👈 MAKE SURE THIS LINE EXISTS
);
This will output in JavaScript as:
const strekAvail = {
123: {
label: 'Stek A',
available_dates: ['2025-08-02', '2025-08-05', ...]
},
...
}
✅ This is good. Leave it as-is.
—
### 🧠 Step 2: Locate your JS function `renderStrekMonthBlock`
In your JS code, find this function:
const renderStrekMonthBlock = (stekId, stekData) => {
Inside this function, you already loop each `day` of the month to generate the calendar.
—
### ✏️ Step 3: ADD this line at the TOP of the function block (after opening):
Right after:
const [year, month] = loadedMonths[idx].split('-').map(Number);
✅ ADD:
const availableDates = Array.isArray(stekData.available_dates) ? stekData.available_dates : [];
This will safely get that strek's available date list.
—
### 🔁 Step 4: In the day loop, UPDATE this block:
Find this block (inside the loop for each day):
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
const isUnavailable = notAvailableDates.includes(dateStr);
const unavailableClass = isUnavailable ? 'unavailable' : '';
✏️ REPLACE with this:
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
const isAvailable = availableDates.includes(dateStr);
const unavailableClass = isAvailable ? '' : 'unavailable';
const disabledAttr = isAvailable ? '' : 'disabled';
—
### ✅ Step 5: Update the “ checkbox line
Find this line (inside the loop):
<input type="checkbox" class="day-checkbox strek-checkbox ${unavailableClass}" id="day-${stekId}-${year}-${month}-${day}" data-date="${dateStr}" data-stek="${stekId}" ${isUnavailable ? 'disabled' : ''}>
✏️ REPLACE with this:
<input type="checkbox" class="day-checkbox strek-checkbox ${unavailableClass}"
id="day-${stekId}-${year}-${month}-${day}"
data-date="${dateStr}"
data-stek="${stekId}"
${disabledAttr}>
✅ This will disable the checkbox and show red for unavailable dates.
—
### 🎨 Step 6: CSS is already working
This class is already in your CSS:
.monthly-calendar td:has(input.unavailable) {
background-color: #ffcccc !important;
}
Optional for green when selected:
.monthly-calendar td.selected {
background: #00c48c !important;
}
—
## ✅ Result:
| Date Type | Visual | Behavior |
|—————|————–|————–|
| Available | Green (on select) | Checkbox enabled |
| Not Available | Red background | Checkbox disabled |
—
Would you like me to help:
– Pre-select the first available date for a strek?
– Add tooltips for unavailable dates?
Let me know!
$days_to_check = apply_filters('yith_booking_resource_availability_days', 90); // Scan next 90 days
$start_date = strtotime('today');
$end_date = strtotime("+{$days_to_check} days");
$strek_avail = array();
foreach ($resources_data as $resource_data) {
$resource = $resource_data->get_resource();
if ($resource) {
$pricing = $resource_data->get_pricing_html();
if (!!$pricing) {
$pricing = '+ ' . $pricing;
}
$description = apply_filters('yith_wcbk_resources_selector_resource_description', $pricing, $resource, $resource_data, $product);
// Collect available dates
$available_dates = [];
for ($date = $start_date; $date
> “But 2025-08-02 to 2025-08-09 are showing in `available_dates[]`, even though they should be unavailable.”
—
### ✅ Root Problem:
You are using:
$is_available = $availability->is_bookable(...);
But **only checking once per date** — and for **weekly bookings**, you're only checking the start and end of the week (`check_in_date` → `check_out_date`).
🧠 However, the `is_bookable()` function doesn't automatically check that **all 7 days are free** — only that the booking from `start` to `end` is bookable **as a range**.
—
## ✅ Step-by-Step Solution (Fix weekly availability logic)
### ✅ 1. Modify the weekly logic to validate **all 7 days** before pushing date into `available_dates[]`.
Replace this part:
$is_available = $availability->is_bookable($product->get_id(), [
'check_in_date' => $week_start,
'check_out_date' => $week_end,
'resource_id' => $resource->get_id(),
]);
### 🔁 With this (check all 7 days individually):
$week_fully_available = true;
for ($d = 0; $d < 7; $d++) {
$check_day = $week_start + ($d * DAY_IN_SECONDS);
$day_is_available = $availability->is_bookable($product->get_id(), [
'check_in_date' => $check_day,
'check_out_date' => $check_day + DAY_IN_SECONDS,
'resource_id' => $resource->get_id(),
]);
if (!$day_is_available) {
$week_fully_available = false;
break;
}
}
if ($week_fully_available) {
$available_dates[] = date('Y-m-d', $week_start); // Only push week start if entire week is available
}
✅ This ensures that for **weekly bookings**, only weeks where **every day** is bookable are included.
—
## ✅ Final Combined Logic
if ($is_weekly_product) {
$week_start = $date;
$week_fully_available = true;
for ($d = 0; $d < 7; $d++) {
$check_day = $week_start + ($d * DAY_IN_SECONDS);
$day_is_available = $availability->is_bookable($product->get_id(), [
'check_in_date' => $check_day,
'check_out_date' => $check_day + DAY_IN_SECONDS,
'resource_id' => $resource->get_id(),
]);
if (!$day_is_available) {
$week_fully_available = false;
break;
}
}
if ($week_fully_available) {
$available_dates[] = date('Y-m-d', $week_start);
}
// Skip next 6 days since this was a full week
$date += 6 * DAY_IN_SECONDS;
} else {
// For daily bookings, check just that day
$is_available = $availability->is_bookable($product->get_id(), [
'check_in_date' => $date,
'check_out_date' => $date + DAY_IN_SECONDS,
'resource_id' => $resource->get_id(),
]);
if ($is_available) {
$available_dates[] = date('Y-m-d', $date);
}
}
—
### ✅ Summary
| Issue | Solution |
|—————————|———————————————–|
| Dates added but not fully available | Loop through **each day of the week** manually |
| `is_bookable()` only checks range | Use daily checks for full 7-day validation |
| Weekly dates incorrectly showing green | Now only weeks with all 7 days available are shown |
—
Would you like this returned as JSON or added directly to your `strekAvail` output for the frontend calendar?
If your **Stek 48 (resource ID 450)** is only available **from 16 August onward**, but your current code shows it as available from **1 August**, that means the problem lies in how the availability is being calculated from:
$availability->is_bookable($product_id, [...]);
This method relies on **how the product and resource availability rules are defined in YITH**, including:
– **Product-level availability rules**
– **Resource-level rules**
– **Global availability rules**
– **Manual exclusions like holidays or fully booked days**
—
## ✅ Step-by-Step Fix & Diagnosis
### 🔎 Step 1: Check if Product or Resource has Manual Availability Rules
In WooCommerce → Bookings:
– Edit **Product (Stek 48 weekly)**
– Check:
– **Availability tab**
– **Resource settings**
– **Any date range rules?**
– **Does resource 450 have its own date restrictions?**
YITH often ignores rules if:
– The **resource does not override global availability**, or
– The availability is based on "booking blocks" but not set per date.
—
### ✅ Step 2: Use the YITH internal API to Debug Real Availability
Try dumping the **raw result** of `is_bookable()`:
$date_to_test = strtotime('2025-08-01');
$available = $availability->is_bookable($product->get_id(), [
'check_in_date' => $date_to_test,
'check_out_date' => $date_to_test + DAY_IN_SECONDS,
'resource_id' => 450,
]);
var_dump('2025-08-01 available?', $available);
Do the same for `2025-08-16`. If both return `true`, then availability rules aren’t applying correctly — or aren't defined at all.
—
### ✅ Step 3: Use YITH Helper Method to See All Disabled Dates (Per Product)
$disabled_days = $product->get_disabled_days(); // Returns array of weekday numbers (0–6)
Or better, if you want to list exact disabled dates:
$excluded = $product->get_excluded_dates(); // array of timestamps or Y-m-d strings
—
### ✅ Step 4: Double-check Resource Settings in Admin
1. Go to: **Bookings → Resources → Adventure Stek 48**
2. Make sure:
– It's **available** from **16 August**
– There are **no rules** allowing bookings before 16 August
– If you're using **global rules**, check if product settings override them
—
### ✅ Step 5: Fix the code to strictly follow resource availability
Add a **resource-level availability check** manually:
if (method_exists($resource, 'is_available_for_date')) {
$is_available = $resource->is_available_for_date($date);
}
⚠️ But this is not always enough, as YITH relies on combined checks. So stick to:
$is_available = $availability->is_bookable(...);
But only **after confirming resource rules** are defined properly.
—
## ✅ Final Suggestion
If `is_bookable()` is returning dates as available that **shouldn’t be**, your **resource or product has no real rules applied**.
So to fix:
– Go to WP Admin → Bookings → Resources
– Edit Stek 48
– Set: **"Available from 16 August 2025"**
– Or define **manual availability rule**
– Then re-run your existing availability scan
—
Would you like help creating a resource availability rule programmatically (via code)? Or want a shortcode to list resource availability to confirm setup visually?
public function check_booked_availability( int $from, int $to, array $args = array() ): bool {
if ( ! $this->is_initialized() ) {
return false;
}
$resource = $this->resource;
$product = $this->product;
$available = true;
$available_quantity = $resource->get_available_quantity();
if ( $available_quantity ) {
$count_persons_as_bookings = $product->has_count_people_as_separate_bookings_enabled();
$persons = isset( $args['persons'] ) ? max( 1, absint( $args['persons'] ) ) : $product->get_minimum_number_of_people();
if ( ! $product->has_people() ) {
$persons = 1;
}
$count_args = array(
'exclude' => $args['exclude'] ?? false,
'exclude_order_id' => $args['exclude_order_id'] ?? false,
'include_bookings_in_cart' => $args['include_bookings_in_cart'] ?? false,
);
$number_of_bookings = $this->count_booked( $from, $to, $count_args );
$booking_weight = $count_persons_as_bookings ? $persons : 1;
if ( $number_of_bookings + $booking_weight > $available_quantity ) {
$available = false;
$remained = $available_quantity – $number_of_bookings;
if ( $remained > 0 ) {
$reason_data = array(
'remained' => $remained,
);
if ( $product->has_people() && $count_persons_as_bookings ) {
$this->add_non_available_reason(
'resource-max-bookings-per-unit',
// translators: %s is the remaining people number.
sprintf( __( 'Too many people selected (%s remaining)', 'yith-booking-for-woocommerce' ), $remained ),
$reason_data
);
} else {
$this->add_non_available_reason(
'resource-max-bookings-per-unit',
// translators: %s is the remaining quantity.
sprintf( __( '%s remaining', 'yith-booking-for-woocommerce' ), $remained ),
$reason_data
);
}
}
}
}
return $available;
}
and want to apply to avaialbe from here by passing resource, from and to date and use in our custo code
public function check_booked_availability(int $from, int $to, array $args = []): bool
This method gives you **the most accurate availability check**, based on:
– booking quantity
– persons
– resource limits
– bookings in cart
– excluded order
– proper reasons
—
## ✅ GOAL
Use this `check_booked_availability()` function in your **custom availability loop** for each resource and day/week.
—
## ✅ STEP-BY-STEP INTEGRATION
—
### ✅ Step 1: Get the Booking Object Instance
In your loop (for each `$resource_data`), get the internal booking logic instance:
$booking_object = yith_wcbk_get_booking_obj( $product, $resource ); // Safely create availability object
> If your plugin already provides it (e.g., `$this` in a class context), reuse it.
—
### ✅ Step 2: Replace `is_bookable()` with `check_booked_availability()`
In your loop (daily or weekly), replace:
$is_available = $availability->is_bookable($product_id, [...]);
With:
$is_available = $booking_object->check_booked_availability($from, $to, [
'resource' => $resource,
'include_bookings_in_cart' => true, // Optional
]);
—
### ✅ Step 3: Full Code (Adapted for Daily + Weekly Logic)
$strek_avail = [];
$days_to_check = 90;
$start_date = strtotime('today');
$end_date = strtotime("+{$days_to_check} days");
foreach ($resources_data as $resource_data) {
$resource = $resource_data->get_resource();
if (!$resource) continue;
$booking_object = yith_wcbk_get_booking_obj($product, $resource);
$available_dates = [];
for ($date = $start_date; $date <= $end_date; $date += DAY_IN_SECONDS) {
$is_weekly_product = strpos($slug, '-dag-boeking') === false;
if ($is_weekly_product) {
$week_start = $date;
$week_end = $week_start + (7 * DAY_IN_SECONDS);
// Validate each day of the week
$week_fully_available = true;
for ($d = 0; $d < 7; $d++) {
$day_start = $week_start + ($d * DAY_IN_SECONDS);
$day_end = $day_start + DAY_IN_SECONDS;
if (!$booking_object->check_booked_availability($day_start, $day_end, ['resource' => $resource])) {
$week_fully_available = false;
break;
}
}
if ($week_fully_available) {
$available_dates[] = date('Y-m-d', $week_start);
$date += 6 * DAY_IN_SECONDS; // skip rest of the week
}
} else {
// Daily booking
$day_end = $date + DAY_IN_SECONDS;
if ($booking_object->check_booked_availability($date, $day_end, ['resource' => $resource])) {
$available_dates[] = date('Y-m-d', $date);
}
}
}
$strek_avail[$resource->get_id()] = [
'label' => $resource->get_name(),
'image_id' => $resource->get_image_id(),
'description' => apply_filters('yith_wcbk_resources_selector_resource_description', '', $resource, $resource_data, $product),
'available_dates' => $available_dates,
];
}
—
## 🧪 Output Example
[
450 => [
'label' => 'Adventure Stek 48',
'available_dates' => ['2025-08-16', '2025-08-23', ...] // Weekly dates now correct ✅
]
]
—
## ✅ Benefits of `check_booked_availability()`
– Respects **resource booking limits**
– Respects **people counts**
– Supports **bookings in cart**
– More accurate than `is_bookable()`
—
Would you like to make this reusable as a helper function? I can wrap this logic so you can call:
get_strek_availability($product);
There is **no global function** called `yith_wcbk_get_booking_obj()`. That was a shorthand I used, but in real YITH code, you need to manually instantiate the booking availability class.
—
## ✅ Correct Way to Use `check_booked_availability()` in Your Custom Code
Here’s how to do it **step by step**, using the correct class directly.
—
### ✅ STEP 1: Use `YITH_WCBK_Availability` Properly with Product + Resource
YITH internally uses:
$booking_object = new YITH_WCBK_Availability( $product, $resource );
So update your code like this:
$booking_object = new YITH_WCBK_Availability( $product, $resource );
—
### ✅ STEP 2: Full Updated Working Code
$strek_avail = [];
$days_to_check = 90;
$start_date = strtotime('today');
$end_date = strtotime("+{$days_to_check} days");
foreach ($resources_data as $resource_data) {
$resource = $resource_data->get_resource();
if (!$resource) continue;
// ✅ Instantiate availability object properly
$booking_object = new YITH_WCBK_Availability($product, $resource);
$available_dates = [];
for ($date = $start_date; $date <= $end_date; $date += DAY_IN_SECONDS) {
$is_weekly_product = strpos($slug, '-dag-boeking') === false;
if ($is_weekly_product) {
$week_start = $date;
$week_end = $week_start + (7 * DAY_IN_SECONDS);
$week_fully_available = true;
for ($d = 0; $d < 7; $d++) {
$day_start = $week_start + ($d * DAY_IN_SECONDS);
$day_end = $day_start + DAY_IN_SECONDS;
$is_day_available = $booking_object->check_booked_availability($day_start, $day_end, [
'resource' => $resource,
'include_bookings_in_cart' => true
]);
if (!$is_day_available) {
$week_fully_available = false;
break;
}
}
if ($week_fully_available) {
$available_dates[] = date('Y-m-d', $week_start);
$date += 6 * DAY_IN_SECONDS; // skip rest of week
}
} else {
$day_end = $date + DAY_IN_SECONDS;
$is_available = $booking_object->check_booked_availability($date, $day_end, [
'resource' => $resource,
'include_bookings_in_cart' => true
]);
if ($is_available) {
$available_dates[] = date('Y-m-d', $date);
}
}
}
$strek_avail[$resource->get_id()] = [
'label' => $resource->get_name(),
'image_id' => $resource->get_image_id(),
'description' => apply_filters('yith_wcbk_resources_selector_resource_description', '', $resource, $resource_data, $product),
'available_dates' => $available_dates,
];
}
—
## ✅ Summary
| ❌ Wrong | ✅ Correct |
|——–|————|
| `yith_wcbk_get_booking_obj()` | `new YITH_WCBK_Availability($product, $resource)` |
This makes sure you're calling `check_booked_availability()` on a properly initialized object.
—
Let me know if you’d like to turn this into a helper function or shortcode!
—
## ✅ GOAL
Use `YITH_WCBK_Resource_Availability_Handler` directly to:
– Check **if a resource is bookable** for a day/week
– Use it in your custom code for frontend availability (e.g. calendar checkboxes)
—
## ✅ STEP-BY-STEP IMPLEMENTATION
### ✅ Step 1: Create and Initialize the Handler
$availability_handler = new YITH_WCBK_Resource_Availability_Handler();
$availability_handler->init($resource, $product); // Pass resource and product
✅ Now `$availability_handler->check_booked_availability($from, $to)` will work.
—
### ✅ Step 2: Replace your loop to use this handler
Here’s your full **updated** availability code using the correct class:
$strek_avail = [];
$days_to_check = 90;
$start_date = strtotime('today');
$end_date = strtotime("+{$days_to_check} days");
foreach ($resources_data as $resource_data) {
$resource = $resource_data->get_resource();
if (!$resource) continue;
$availability_handler = new YITH_WCBK_Resource_Availability_Handler();
$availability_handler->init($resource, $product);
$available_dates = [];
for ($date = $start_date; $date <= $end_date; $date += DAY_IN_SECONDS) {
$is_weekly_product = strpos($slug, '-dag-boeking') === false;
if ($is_weekly_product) {
$week_start = $date;
$week_fully_available = true;
for ($i = 0; $i < 7; $i++) {
$day_start = $week_start + ($i * DAY_IN_SECONDS);
$day_end = $day_start + DAY_IN_SECONDS;
if (!$availability_handler->check_booked_availability($day_start, $day_end, [
'include_bookings_in_cart' => true
])) {
$week_fully_available = false;
break;
}
}
if ($week_fully_available) {
$available_dates[] = date('Y-m-d', $week_start);
$date += 6 * DAY_IN_SECONDS; // skip rest of week
}
} else {
$day_end = $date + DAY_IN_SECONDS;
if ($availability_handler->check_booked_availability($date, $day_end, [
'include_bookings_in_cart' => true
])) {
$available_dates[] = date('Y-m-d', $date);
}
}
}
$strek_avail[$resource->get_id()] = [
'label' => $resource->get_name(),
'image_id' => $resource->get_image_id(),
'description' => apply_filters('yith_wcbk_resources_selector_resource_description', '', $resource, $resource_data, $product),
'available_dates' => $available_dates,
];
}
—
## ✅ What You Gain with This:
✅ Accurate availability checking
✅ Includes logic for:
– Max resource quantity
– Person-based bookings
– Cart items
– Duration unit
– Manual exclusion
✅ No duplicate logic — it uses YITH's core system!
—
### ✅ Final Tips
– You do **not need to use `YITH_WCBK_Availability` anymore**.
– You do **not need to check per-day logic yourself** — this handler does it all.
– You can extend it later to get remaining slots or reasons via:
$availability_handler->get_non_available_reasons()
—
Would you like a helper function like `get_available_dates_for_resource($resource, $product)` for reuse?
<style>
/* Toggle Button */
.toggle-btn {
transition: background 0.2s, color 0.2s;
}
.toggle-btn.active {
background: #00c48c !important;
color: #fff !important;
}
/* Calendar Containers */
.weekly-calendar,
.monthly-calendar {
margin: 30px 0;
font-family: Arial, sans-serif;
}
/* Table Styling */
.weekly-calendar table,
.monthly-calendar table {
width: 100%;
border-collapse: collapse;
text-align: center;
min-width: 220px;
margin-bottom: 0;
}
/* Table Headers and Cells */
.weekly-calendar th,
.weekly-calendar td,
.monthly-calendar th,
.monthly-calendar td {
border: none;
}
.weekly-calendar th {
text-align: center;
vertical-align: middle;
font-size: 12px;
}
.weekly-calendar td {
font-size: 14px !important;
font-weight: 600 !important;
}
.weekly-calendar th small {
font-size: 10px;
color: #666;
display: block;
margin-top: 2px;
}
.weekly-calendar th,
.monthly-calendar th {
}
.yith-wcbk-booking-form button#prev-week-btn ,.yith-wcbk-booking-form button#next-week-btn{
background: none;
border:none;
cursor: pointer;
}
.yith-wcbk-form-section-dates-wrapper .toggle-btn {
padding: 10px !important;
text-align: center;
font-size: 14px;
font-weight: 600;
}
.weekly-calendar .selected,
.monthly-calendar .selected {
color: #fff;
}
/* Monthly Calendar Layout */
.monthly-calendar {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.prev_month ,.next_month {
background: none;
border: none;
cursor: pointer;
}
.monthly-calendar caption {
font-weight: bold;
margin-bottom: 5px;
}
.monthly-calendar .day-checkbox {
display: none;
}
/* 2. Style the label as a box */
label[for^="day-"] {
display: inline-block;
text-align: center;
font-weight: bold;
position: relative;
cursor: pointer;
}
/* 3. Show tick icon when checked */
.day-checkbox:checked + label[for^="day-"]::after {
content: "✔";
color: green;
font-size: 19px;
position: absolute;
top: -5px;
right: 0px;
background: #ccffcc;
width: 100%;
}
.monthly-calendar td:has(input.unavailable) {
background-color: #ffcccc !important;
border-radius: 5px;
padding: 10px !important;
}
.monthly-calendar td.selected{
background: #00c48c !important;
border-radius: 5px;
padding: 10px !important;
}
.monthly-calendar td:not(:first-child) {
background: #ccffcc;
border-radius: 5px;
}
/* Scroll Container */
.monthly-calendar-scroll {
width: 100%;
overflow-y: auto;
}
.monthly-calendar-scroll.scrollable {
max-height: 350px;
}
/* Calendar Block */
.strek-calendar-block {
margin-bottom: 24px;
background: #fff;
padding: 8px 0 16px 0;
}
.weekly-calendar .strek-calendar-block:last-child {
border-bottom: none;
}
.weekly-calendar td:has(input.unavailable) {
background-color: #ffcccc !important;
}
.weekly-calendar td.selected{
background: #00c48c !important;
}
/* Custom Checkbox */
.weekly-calendar .strek-checkbox {
appearance: none;
-webkit-appearance: none;
width: 20px;
height: 20px;
border: 2px solid #ccc;
border-radius: 4px;
outline: none;
cursor: pointer;
position: relative;
vertical-align: middle; /* default: available (green) */
transition: background 0.2s;
}
.weekly-calendar .strek-checkbox:checked::after {
content: "✔";
display: inline-block;
color: #ffffff;
font-size: 19px;
position: relative;
left: 2px;
top: 0px;
}
.weekly-calendar .week-checkbox.strek-checkbox.unavailable::after {
content: "✖"; /* Unicode cross */
color: red;
font-size: 19px;
position: absolute;
top: 0;
left: 2px;
}
.weekly-calendar .strek-checkbox.unavailable {
background: #ffcccc !important; /* unavailable (red) */
border-color: #ff8888;
border:none;
}
.weekly-calendar .strek-checkbox:disabled {
cursor: not-allowed;
opacity: 0.7;
}
.weekly-calendar .strek-checkbox:checked {
background: #00c48c !important;
border-color: #00c48c;
border:none;
}
/* Weekly calendar specific styles */
.weekly-calendar td {
text-align: center;
vertical-align: middle;
position: relative;
padding: 0px !important;
}
.weekly-calendar td:not(:first-child){
background: #ccffcc;
border-radius: 5px;
}
.weekly-calendar tr{
border:none !important;
}
td small {
font-size: 10px;
line-height: 1.2;
margin-top: 4px;
display: none;
}
.woocommerce div.product form.cart table{
border-collapse: separate;
border-spacing: 12px;
}
</style>
<script>
let displayWeekly='<?php echo $displayWeekly; ?>';
let displayPerday='<?php echo $displayPerday; ?>';
const strekAvail = <?php echo json_encode($strek_avail); ?>;
const strekMonthIdx = {};
Object.keys(strekAvail).forEach(stekId => {
strekMonthIdx[stekId] = 0; // Start at first month for each strek
});
const allowedDays = <?php echo json_encode($product->get_allowed_start_days()); ?>; // 1=Monday, 5=Friday, etc.
console.log(strekAvail);
document.addEventListener('DOMContentLoaded', function() {
const weekList = <?php echo json_encode($week_list); ?>;
const weeksToShow = <?php echo $weeks_to_show; ?>;
const loadedMonths = <?php echo json_encode($loaded_months); ?>;
let weekOffset = 0;
let currentMonthIdx = 0;
const notAvailableDates = <?php echo json_encode($not_available_dates); ?>;
// Helper function to check if any day in a week is unavailable
const weekHasUnavailableDates = (weekDates, notAvailableDates) => {
for (let i = 0; i < weekDates.length; i++) {
if (notAvailableDates.includes(weekDates[i])) {
return true;
}
}
return false;
};
// WEEKLY CALENDAR
const renderCalendar = () => {
let html = `<table><thead><tr><th>Stek</th>`;
// Table headers (weeks)
for (let i = 0; i < weeksToShow; i++) {
const weekIdx = weekOffset + i;
if (weekIdx >= weekList.length) break;
const w = weekList[weekIdx];
// Find allowed weekday (e.g. Friday) in this week
let allowedDate = null;
for (let d = 0; d < w.dates.length; d++) {
const dateObj = new Date(w.dates[d]);
// getDay: 0=Sunday, 1=Monday, ..., 5=Friday
// allowedDays uses 1=Monday, 7=Sunday (ISO)
const isoDay = dateObj.getDay() === 0 ? 7 : dateObj.getDay();
if (allowedDays.includes(isoDay)) {
allowedDate = w.dates[d];
break;
}
}
let thLabel = `Week ${w.week}`;
if (allowedDate) {
const dateObj = new Date(allowedDate);
thLabel += `<br><small>${dateObj.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' })}</small>`;
}
html += `<th>${thLabel}</th>`;
}
html += `</tr></thead><tbody>`;
// For each stek, create a row
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
html += `<tr><td>${stekData.label}</td>`;
for (let i = 0; i < weeksToShow; i++) {
const weekIdx = weekOffset + i;
if (weekIdx >= weekList.length) break;
const w = weekList[weekIdx];
// Find allowed weekday in this week
let allowedDate = null;
for (let d = 0; d < w.dates.length; d++) {
const dateObj = new Date(w.dates[d]);
const isoDay = dateObj.getDay() === 0 ? 7 : dateObj.getDay();
if (allowedDays.includes(isoDay)) {
allowedDate = w.dates[d];
break;
}
}
// ✅ Fix: Use `stekData.available_dates`
const isAvailable = allowedDate && stekData.available_dates.includes(allowedDate);
const unavailableClass = isAvailable ? '' : 'unavailable';
html += `<td style="padding: 20px 10px;">
<input type="checkbox" class="week-checkbox strek-checkbox ${unavailableClass}"
id="week-check-${stekId}-${weekIdx}"
data-stek="${stekId}"
data-week="${w.week}"
data-year="${w.year}"
data-date="${allowedDate ? allowedDate : ''}"
${isAvailable ? '' : 'disabled'}>
${allowedDate
? `<br><small>${new Date(allowedDate).toLocaleDateString('nl-NL', { weekday: 'short', day: '2-digit', month: '2-digit' })}</small>`
: '<br><small>n.v.t.</small>'}
</td>`;
}
html += `</tr>`;
});
html += `</tbody></table>`;
document.getElementById('calendar-table-wrapper').innerHTML = html;
// Update navigation label
const navLabel = document.getElementById('week-navigation-label');
if (navLabel && weekList.length > 0) {
const firstWeek = weekList[weekOffset];
const lastWeek = weekList[Math.min(weekOffset + weeksToShow - 1, weekList.length - 1)];
if (firstWeek && lastWeek) {
const firstDate = new Date(firstWeek.start);
const lastDate = new Date(lastWeek.start);
lastDate.setDate(lastDate.getDate() + 6);
navLabel.textContent = `Weeks ${firstWeek.week} - ${lastWeek.week} (${firstDate.toLocaleDateString('nl-NL')} - ${lastDate.toLocaleDateString('nl-NL')})`;
}
}
// Selection highlight for checkboxes and event handlers
document.querySelectorAll('.weekly-calendar .week-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', () => {
const td = checkbox.closest('td');
if (checkbox.checked) {
// Uncheck all other week checkboxes across all streks
document.querySelectorAll('.weekly-calendar .week-checkbox').forEach(cb => {
if (cb !== checkbox) {
cb.checked = false;
cb.closest('td').classList.remove('selected');
}
});
td.classList.add('selected');
// Update the booking form fields
const strekId = checkbox.getAttribute('data-stek');
const productId = "<?php echo $product->get_id(); ?>";
const selectedDate = checkbox.getAttribute('data-date');
// Set the check-in (start date) field
const startDateInput = document.getElementById(`yith-wcbk-booking-start-date-${productId}`);
if (startDateInput) {
startDateInput.value = selectedDate;
startDateInput.dispatchEvent(new Event('input', { bubbles: true }));
startDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(startDateInput).datepicker) {
jQuery(startDateInput).datepicker('setDate', selectedDate);
}
}
// Set the check-out (end date) field to 7 days after check-in
const endDateInput = document.getElementById(`yith-wcbk-booking-end-date-${productId}`);
if (endDateInput) {
const dateObj = new Date(selectedDate);
dateObj.setDate(dateObj.getDate() + 7); // 7 days (week)
const yyyy = dateObj.getFullYear();
const mm = String(dateObj.getMonth() + 1).padStart(2, '0');
const dd = String(dateObj.getDate()).padStart(2, '0');
const endDate = `${yyyy}-${mm}-${dd}`;
endDateInput.value = endDate;
endDateInput.dispatchEvent(new Event('input', { bubbles: true }));
endDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(endDateInput).datepicker) {
jQuery(endDateInput).datepicker('setDate', endDate);
}
}
// Update strek dropdown
const select = document.querySelector(`#yith-wcbk-booking-resources-${productId} select.yith-wcbk-selector__field`);
if (select) {
select.value = strekId;
const event = new Event('change', { bubbles: true });
select.dispatchEvent(event);
const customItem = document.querySelector(`#yith-wcbk-booking-resources-${productId} .yith-wcbk-selector__item[data-key="${strekId}"]`);
if (customItem) {
customItem.click();
}
}
} else {
td.classList.remove('selected');
}
});
});
};
document.getElementById('prev-week-btn')?.remove();
document.getElementById('next-week-btn')?.remove();
const navDiv = document.createElement('div');
navDiv.style.display = "flex";
navDiv.style.justifyContent = "center";
navDiv.style.marginBottom = "10px";
navDiv.innerHTML = `
<button type="button" id="prev-week-btn" style="display:<?php echo $displayWeekly; ?>;"> <img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/left-chevron-svgrepo-com.svg" alt="Previous" width="25" height="25"> </button>
<div style="flex: 1; text-align: center; font-weight: bold;display:<?php echo $displayWeekly; ?>;" id="week-navigation-label">Select a week</div>
<button type="button" id="next-week-btn" style="display:<?php echo $displayWeekly; ?>;"> <img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/right-chevron-svgrepo-com.svg" alt="Previous" width="25" height="25"> </button>
`;
document.getElementById('calendar-table-wrapper').before(navDiv);
document.getElementById('prev-week-btn').addEventListener('click', () => {
if (weekOffset - weeksToShow >= 0) {
weekOffset -= weeksToShow;
} else {
weekOffset = 0;
}
renderCalendar();
});
document.getElementById('next-week-btn').addEventListener('click', () => {
if (weekOffset + weeksToShow < weekList.length) {
weekOffset += weeksToShow;
renderCalendar();
}
});
// MONTHLY CALENDAR (one month at a time)
const renderMonthlyCalendar = () => {
if (loadedMonths.length === 0) {
document.getElementById('monthly-calendar-scroll').innerHTML = '<p>No months available.</p>';
document.getElementById('monthly-calendar-controls').style.display = 'none';
return;
}
document.getElementById('monthly-calendar-controls').style.display = 'none'; // Hide global controls
let html = '';
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
html += renderStrekMonthBlock(stekId, stekData);
});
document.getElementById('monthly-calendar-scroll').innerHTML = html;
// Add event listeners for each strek's navigation
Object.keys(strekAvail).forEach(stekId => {
document.getElementById(`prev-month-btn-${stekId}`).addEventListener('click', () => {
if (strekMonthIdx[stekId] > 0) {
strekMonthIdx[stekId]--;
renderMonthlyCalendar();
}
});
document.getElementById(`next-month-btn-${stekId}`).addEventListener('click', () => {
if (strekMonthIdx[stekId] < loadedMonths.length - 1) {
strekMonthIdx[stekId]++;
renderMonthlyCalendar();
}
});
});
// Add scrollable class if needed
const strekCount = Object.keys(strekAvail).length;
const scrollDiv = document.getElementById('monthly-calendar-scroll');
if (strekCount > 2) {
scrollDiv.classList.add('scrollable');
} else {
scrollDiv.classList.remove('scrollable');
}
};
const renderStrekMonthBlock = (stekId, stekData) => {
const idx = strekMonthIdx[stekId];
const [year, month] = loadedMonths[idx].split('-').map(Number);
const availableDates = Array.isArray(stekData.available_dates) ? stekData.available_dates : [];
// Get first and last day of month
const firstDay = new Date(year, month - 1, 1);
const lastDay = new Date(year, month, 0);
const daysInMonth = lastDay.getDate();
let html = `<div class="strek-calendar-block">
<div style="font-weight:bold; margin-bottom:20px;">${stekData.label}</div>
<div style="text-align:center; margin-bottom:10px; display:flex; align-items: center; justify-content: space-between;">
<button type="button" id="prev-month-btn-${stekId}" class="prev_month"><img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/left-chevron-svgrepo-com.svg" alt="Next" width="25" height="25"> </button>
<span style="font-weight:bold; margin:0 10px;">${firstDay.toLocaleString('default', { month: 'long', year: 'numeric' })}</span>
<button type="button" id="next-month-btn-${stekId}" class="next_month"><img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/right-chevron-svgrepo-com.svg" alt="Next" width="25" height="25"></button>
</div>
<table>
<thead>
<tr>
<th>Mo</th><th>Tu</th><th>We</th><th>Th</th><th>Fr</th><th>Sa</th><th>Su</th>
</tr>
</thead>
<tbody>
<tr>`;
// Calculate the day of week for the 1st (Monday=0)
let dayOfWeek = (firstDay.getDay() + 6) % 7;
for (let i = 0; i < dayOfWeek; i++) html += '<td></td>';
for (let day = 1; day <= daysInMonth; day++) {
if ((dayOfWeek % 7 === 0) && day !== 1) html += '</tr><tr>';
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
const isAvailable = availableDates.includes(dateStr);
const unavailableClass = isAvailable ? '' : 'unavailable';
const disabledAttr = isAvailable ? '' : 'disabled';
html += `<td>
<input type="checkbox" class="day-checkbox strek-checkbox ${unavailableClass}"
id="day-${stekId}-${year}-${month}-${day}"
data-date="${dateStr}"
data-stek="${stekId}"
${disabledAttr}>
<label for="day-${stekId}-${year}-${month}-${day}">${day}</label>
</td>`;
dayOfWeek++;
}
while (dayOfWeek % 7 !== 0) { html += '<td></td>'; dayOfWeek++; }
html += '</tr></tbody></table></div>';
return html;
};
const renderSingleMonth = (year, month) => {
// For each strek, render a classic calendar
let html = '';
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
// Get first and last day of month
const firstDay = new Date(year, month - 1, 1);
const lastDay = new Date(year, month, 0);
const daysInMonth = lastDay.getDate();
html += `<div class="strek-calendar-block">
<div style="font-weight:bold; margin-bottom:4px;">${stekData.label}</div>
<table>
<caption>${firstDay.toLocaleString('default', { month: 'long' })} ${year}</caption>
<thead>
<tr>
<th>Mo</th><th>Tu</th><th>We</th><th>Th</th><th>Fr</th><th>Sa</th><th>Su</th>
</tr>
</thead>
<tbody>
<tr>`;
// Calculate the day of week for the 1st (Monday=0)
let dayOfWeek = (firstDay.getDay() + 6) % 7;
for (let i = 0; i < dayOfWeek; i++) html += '<td></td>';
for (let day = 1; day <= daysInMonth; day++) {
if ((dayOfWeek % 7 === 0) && day !== 1) html += '</tr><tr>';
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
html += `<td>
<input type="checkbox" class="day-checkbox strek-checkbox" id="day-${stekId}-${year}-${month}-${day}" data-date="${dateStr}" data-stek="${stekId}">
<label for="day-${stekId}-${year}-${month}-${day}">${day}</label>
</td>`;
dayOfWeek++;
}
while (dayOfWeek % 7 !== 0) { html += '<td></td>'; dayOfWeek++; }
html += '</tr></tbody></table></div>';
});
return html;
};
// TOGGLE LOGIC
document.getElementById('toggle-week').addEventListener('click', function() {
document.getElementById('toggle-week').classList.add('active');
document.getElementById('toggle-day').classList.remove('active');
document.getElementById('calendar-table-wrapper').style.display = '';
document.getElementById('monthly-calendar-wrapper').style.display = 'none';
document.getElementById('monthly-calendar-controls').style.display = 'none';
navDiv.style.display = '';
renderCalendar();
});
document.getElementById('toggle-day').addEventListener('click', function() {
document.getElementById('toggle-day').classList.add('active');
document.getElementById('toggle-week').classList.remove('active');
document.getElementById('calendar-table-wrapper').style.display = 'none';
document.getElementById('monthly-calendar-wrapper').style.display = '';
document.getElementById('monthly-calendar-controls').style.display = '';
navDiv.style.display = 'none';
renderMonthlyCalendar();
});
// Initial render
if(displayWeekly=='block'){
renderCalendar();
}else{
renderMonthlyCalendar();
}
document.getElementById('prev-month-btn').addEventListener('click', () => {
if (currentMonthIdx > 0) {
currentMonthIdx--;
renderMonthlyCalendar();
}
});
document.getElementById('next-month-btn').addEventListener('click', () => {
if (currentMonthIdx < loadedMonths.length - 1) {
currentMonthIdx++;
renderMonthlyCalendar();
}
});
document.addEventListener('change', function(e) {
if (
e.target.classList.contains('day-checkbox') &&
e.target.checked &&
!e.target.disabled
) {
// Uncheck all other day-checkboxes
document.querySelectorAll('.day-checkbox').forEach(cb => {
if (cb !== e.target) cb.checked = false;
});
const strekId = e.target.getAttribute('data-stek');
const productId = "<?php echo $product->get_id(); ?>";
const selectedDate = e.target.getAttribute('data-date');
// Set the check-in (start date) field and fire change event
const startDateInput = document.getElementById(`yith-wcbk-booking-start-date-${productId}`);
startDateInput.value = selectedDate;
// Fire both input and change events
startDateInput.dispatchEvent(new Event('input', { bubbles: true }));
startDateInput.dispatchEvent(new Event('change', { bubbles: true }));
// If a jQuery datepicker is attached, update it as well
if (typeof jQuery !== 'undefined' && jQuery(startDateInput).datepicker) {
jQuery(startDateInput).datepicker('setDate', selectedDate);
}
// Set the check-out (end date) field to one day after check-in
const endDateInput = document.getElementById(`yith-wcbk-booking-end-date-${productId}`);
if (endDateInput) {
// Calculate next day
const dateObj = new Date(selectedDate);
dateObj.setDate(dateObj.getDate() + 1);
// Format as yyyy-mm-dd
const yyyy = dateObj.getFullYear();
const mm = String(dateObj.getMonth() + 1).padStart(2, '0');
const dd = String(dateObj.getDate()).padStart(2, '0');
const nextDay = `${yyyy}-${mm}-${dd}`;
endDateInput.value = nextDay;
endDateInput.dispatchEvent(new Event('input', { bubbles: true }));
endDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(endDateInput).datepicker) {
jQuery(endDateInput).datepicker('setDate', nextDay);
}
}
// Strek dropdown logic (as before)
const select = document.querySelector(`#yith-wcbk-booking-resources-${productId} select.yith-wcbk-selector__field`);
if (select) {
select.value = strekId;
const event = new Event('change', { bubbles: true });
select.dispatchEvent(event);
const customItem = document.querySelector(`#yith-wcbk-booking-resources-${productId} .yith-wcbk-selector__item[data-key="${strekId}"]`);
if (customItem) {
customItem.click();
}
}
}
});
});
</script>
okay i think now we have correct available dates , now manage weekly calender based on it
becauseas per outrout
Array ( [450] => Array ( [label] => Adventure Stek 48 [image_id] => 451 [description] => [available_dates] => Array ( [0] => 2025-08-16 [1] => 2025-08-23 [2] => 2025-08-30 [3] => 2025-09-06 [4] => 2025-09-13 [5] => 2025-09-20 [6] => 2025-09-27 [7] => 2025-10-04 [8] => 2025-10-11 [9] => 2025-10-18 [10] => 2025-10-25 ) ) [452] => Array ( [label] => Adventure Stek 49 [image_id] => 453 [description] => [available_dates] => Array ( [0] => 2025-08-01 [1] => 2025-08-16 [2] => 2025-08-23 [3] => 2025-08-30 [4] => 2025-09-06 [5] => 2025-09-13 [6] => 2025-09-20 [7] => 2025-09-27 [8] => 2025-10-04 [9] => 2025-10-11 [10] => 2025-10-18 [11] => 2025-10-25 ) ) [454] => Array ( [label] => Adventure Stek 50 [image_id] => 455 [description] => [available_dates] => Array ( [0] => 2025-08-01 [1] => 2025-08-08 [2] => 2025-08-15 [3] => 2025-08-22 [4] => 2025-08-29 [5] => 2025-09-05 [6] => 2025-09-12 [7] => 2025-09-19 [8] => 2025-09-26 [9] => 2025-10-03 [10] => 2025-10-10 [11] => 2025-10-17 [12] => 2025-10-24 ) ) [456] => Array ( [label] => Adventure Stek 51 [image_id] => 457 [description] => [available_dates] => Array ( [0] => 2025-08-01 [1] => 2025-08-08 [2] => 2025-08-15 [3] => 2025-08-22 [4] => 2025-08-29 [5] => 2025-09-05 [6] => 2025-09-12 [7] => 2025-09-19 [8] => 2025-09-26 [9] => 2025-10-03 [10] => 2025-10-10 [11] => 2025-10-17 [12] => 2025-10-24 ) ) [
for reosurce 450 its showiscrrect in week calender but for 452
Available dates are
Array ( [0] => 2025-08-01 [1] => 2025-08-16 [2] => 2025-08-23 [3] => 2025-08-30 [4] => 2025-09-06 [5] => 2025-09-13 [6] => 2025-09-20 [7] => 2025-09-27 [8] => 2025-10-04 [9] => 2025-10-11 [10] => 2025-10-18 [11] => 2025-10-25 )
but in calednt its still red for week but still not showing week for 2025-08-01
—
### ✅ Full Reusable Function
function get_available_dates_for_resource($resource, $product, $days_to_check = 90) {
if (!$resource || !$product) return [];
$availability_handler = new YITH_WCBK_Resource_Availability_Handler();
$availability_handler->init($resource, $product);
$start_date = strtotime('today');
$end_date = strtotime("+{$days_to_check} days");
$available_dates = [];
// Detect if weekly or daily booking product
$slug = $product->get_slug(); // assuming this gives product slug
$is_weekly_product = strpos($slug, '-dag-boeking') === false;
for ($date = $start_date; $date <= $end_date; $date += DAY_IN_SECONDS) {
if ($is_weekly_product) {
$week_start = $date;
$week_fully_available = true;
for ($i = 0; $i < 7; $i++) {
$day_start = $week_start + ($i * DAY_IN_SECONDS);
$day_end = $day_start + DAY_IN_SECONDS;
if (!$availability_handler->check_booked_availability($day_start, $day_end, [
'include_bookings_in_cart' => true
])) {
$week_fully_available = false;
break;
}
}
if ($week_fully_available) {
$available_dates[] = date('Y-m-d', $week_start);
$date += 6 * DAY_IN_SECONDS; // skip rest of week
}
} else {
$day_end = $date + DAY_IN_SECONDS;
if ($availability_handler->check_booked_availability($date, $day_end, [
'include_bookings_in_cart' => true
])) {
$available_dates[] = date('Y-m-d', $date);
}
}
}
return $available_dates;
}
—
### ✅ Example Usage
$strek_avail = [];
foreach ($resources_data as $resource_data) {
$resource = $resource_data->get_resource();
if (!$resource) continue;
$available_dates = get_available_dates_for_resource($resource, $product);
$strek_avail[$resource->get_id()] = [
'label' => $resource->get_name(),
'image_id' => $resource->get_image_id(),
'description' => apply_filters('yith_wcbk_resources_selector_resource_description', '', $resource, $resource_data, $product),
'available_dates' => $available_dates,
];
}
—
### ✅ Output Example (`$strek_avail`)
[
450 => [
'label' => 'Adventure Stek 48',
'image_id' => 451,
'description' => '',
'available_dates' => ['2025-08-16', '2025-08-23', ...]
],
...
]
Let me know if you want to convert this to return **booked vs available JSON for full calendar month**.
// Table headers (weeks)
for (let i = 0; i < weeksToShow; i++) {
const weekIdx = weekOffset + i;
if (weekIdx >= weekList.length) break;
const w = weekList[weekIdx];
// Find allowed weekday (e.g. Friday) in this week
let allowedDate = null;
for (let d = 0; d < w.dates.length; d++) {
const dateObj = new Date(w.dates[d]);
// getDay: 0=Sunday, 1=Monday, …, 5=Friday, 6=Saturday
// allowedDays uses 1=Monday, 7=Sunday (ISO)
// Convert to ISO format: Saturday=6 becomes 6, Sunday=0 becomes 7
const isoDay = dateObj.getDay() === 0 ? 7 : dateObj.getDay();
if (allowedDays.includes(isoDay)) {
allowedDate = w.dates[d];
break;
}
}
let thLabel = `Week ${w.week}`;
if (allowedDate) {
const dateObj = new Date(allowedDate);
thLabel += `${dateObj.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' })}`;
}
html += `${thLabel}`;
}
html += “;
change below line to show
25-10 to 28-10
thLabel += `${dateObj.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' })}`;
—
### ✅ Updated Line:
Replace this:
thLabel += `<br><small>${dateObj.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' })}</small>`;
With this:
const startObj = new Date(w.dates[0]);
const endObj = new Date(w.dates[w.dates.length - 1]);
const startStr = startObj.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' });
const endStr = endObj.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' });
thLabel += `<br><small>${startStr} to ${endStr}</small>`;
—
### ✅ Result Example:
<th>Week 43<br><small>25-10 to 28-10</small></th>
Let me know if you want to highlight weekends or mark available days directly in the table too.
<style>
/* Toggle Button */
.toggle-btn {
transition: background 0.2s, color 0.2s;
}
.toggle-btn.active {
background: #00c48c !important;
color: #fff !important;
}
/* Calendar Containers */
.weekly-calendar,
.monthly-calendar {
margin: 30px 0;
font-family: Arial, sans-serif;
}
/* Table Styling */
.weekly-calendar table,
.monthly-calendar table {
width: 100%;
border-collapse: collapse;
text-align: center;
min-width: 220px;
margin-bottom: 0;
}
/* Table Headers and Cells */
.weekly-calendar th,
.weekly-calendar td,
.monthly-calendar th,
.monthly-calendar td {
border: none;
}
.weekly-calendar th {
text-align: center;
vertical-align: middle;
font-size: 12px;
}
.weekly-calendar td {
font-size: 14px !important;
font-weight: 600 !important;
}
.weekly-calendar th small {
font-size: 10px;
color: #666;
display: block;
margin-top: 2px;
}
.weekly-calendar th,
.monthly-calendar th {
}
.yith-wcbk-booking-form button#prev-week-btn ,.yith-wcbk-booking-form button#next-week-btn{
background: none;
border:none;
cursor: pointer;
}
.yith-wcbk-form-section-dates-wrapper .toggle-btn {
padding: 10px !important;
text-align: center;
font-size: 14px;
font-weight: 600;
}
.weekly-calendar .selected,
.monthly-calendar .selected {
color: #fff;
}
/* Monthly Calendar Layout */
.monthly-calendar {
display: flex;<?php
/**
* Start date field in booking form
* This template can be overridden by copying it to yourtheme/woocommerce/single-product/add-to-cart/booking-form/dates/start-date.php
*
* @var WC_Product_Booking $product The booking product.
* @var array $date_info
* @var array $not_available_dates
* @var string $calendar_day_range_picker_class
* @package YITHBookingTemplates
*/
defined( 'YITH_WCBK' ) || exit;
// --- Product Slug and Price Setup ---
$slug = $product->get_slug();
$perday_product_url = 'javascript:void(0)';
$weekly_product_url = 'javascript:void(0)';
$weeklyActive = '';
$perdayActive = '';
$displayWeekly = 'none';
$displayPerday = 'none';
if (strpos($slug, '-dag-boeking') !== false) {
// Per-day product
$perday_price = $product->get_price_html();
$weekly_slug = str_replace('-dag-boeking', '', $slug);
$weekly_product = get_page_by_path($weekly_slug, OBJECT, 'product');
$weekly_product_url = get_permalink($weekly_product->ID);
$perdayActive = 'active';
$displayPerday = 'block';
$weekly_price = '';
if ($weekly_product) {
$weekly_product_obj = wc_get_product($weekly_product->ID);
$weekly_price = $weekly_product_obj ? $weekly_product_obj->get_price_html() : '';
}
$resources_data = $product->get_resources_data();
} else {
// Weekly product
$weeklyActive = 'active';
$displayWeekly = 'block';
$weekly_price = $product->get_price_html();
$perday_slug = $slug . '-dag-boeking';
$perday_product = get_page_by_path($perday_slug, OBJECT, 'product');
$perday_product_url = get_permalink($perday_product->ID);
$perday_price = '';
if ($perday_product) {
$perday_product_obj = wc_get_product($perday_product->ID);
$perday_price = $perday_product_obj ? $perday_product_obj->get_price_html() : '';
}
$resources_data = $product->get_resources_data();
}
// Fallback: If not found, use current product price for both
if (empty($weekly_price)) $weekly_price = $product->get_price_html();
if (empty($perday_price)) $perday_price = $product->get_price_html();
// --- Resource Data ---
$availability = new YITH_WCBK_Availability();
$days_to_check = apply_filters('yith_booking_resource_availability_days', 90); // Scan next 90 days
$start_date = strtotime('today');
$end_date = strtotime("+{$days_to_check} days");
$strek_avail = array();
foreach ($resources_data as $resource_data) {
$resource = $resource_data->get_resource();
if (!$resource) continue;
$availability_handler = new YITH_WCBK_Resource_Availability_Handler();
$availability_handler->init($resource, $product);
$available_dates = [];
for ($date = $start_date; $date <= $end_date; $date += DAY_IN_SECONDS) {
$is_weekly_product = strpos($slug, '-dag-boeking') === false;
if ($is_weekly_product) {
$week_start = $date;
// Check each day in the week to see if it's an allowed start day
for ($i = 0; $i < 7; $i++) {
$day_start = $week_start + ($i * DAY_IN_SECONDS);
$day_end = $day_start + DAY_IN_SECONDS;
// Get the day of week (1=Monday, 7=Sunday)
$day_of_week = date('N', $day_start);
// Check if this day is an allowed start day
$allowed_start_days = $product->get_allowed_start_days();
if (in_array($day_of_week, $allowed_start_days)) {
// Check if this specific day is available
if ($availability_handler->check_booked_availability($day_start, $day_end, [
'include_bookings_in_cart' => true
])) {
$available_dates[] = date('Y-m-d', $day_start);
}
}
}
// Skip to next week
$date += 6 * DAY_IN_SECONDS;
} else {
$day_end = $date + DAY_IN_SECONDS;
if ($availability_handler->check_booked_availability($date, $day_end, [
'include_bookings_in_cart' => true
])) {
$available_dates[] = date('Y-m-d', $date);
}
}
}
$strek_avail[$resource->get_id()] = [
'label' => $resource->get_name(),
'image_id' => $resource->get_image_id(),
'description' => apply_filters('yith_wcbk_resources_selector_resource_description', '', $resource, $resource_data, $product),
'available_dates' => $available_dates,
];
}
$all_day_booking = $product->is_full_day();
list(
$current_year,
$current_month,
$next_year,
$next_month,
$min_date,
$min_date_timestamp,
$max_date,
$max_date_timestamp,
$default_start_date,
$default_end_date,
$months_to_load,
$ajax_load_months,
$loaded_months
) = yith_plugin_fw_extract(
$date_info,
'current_year', 'current_month', 'next_year', 'next_month',
'min_date', 'min_date_timestamp', 'max_date', 'max_date_timestamp',
'default_start_date', 'default_end_date', 'months_to_load', 'ajax_load_months', 'loaded_months'
);
?>
<!-- Start Date Field (Hidden) -->
<div class="yith-wcbk-form-section yith-wcbk-form-section-dates <?php echo esc_attr($calendar_day_range_picker_class); ?>" style='display: none;'>
<label for="yith-wcbk-booking-start-date-<?php echo esc_attr($product->get_id()); ?>" class="yith-wcbk-form-section__label yith-wcbk-booking-form__label">
<?php echo esc_html(yith_wcbk_get_label('start-date')); ?>
</label>
<div class="yith-wcbk-form-section__content">
<?php
yith_wcbk_print_field(array(
'type' => yith_wcbk()->settings->display_date_picker_inline() ? 'datepicker-inline' : 'datepicker',
'id' => 'yith-wcbk-booking-start-date-' . $product->get_id(),
'name' => 'start-date',
'class' => 'yith-wcbk-booking-date yith-wcbk-booking-start-date',
'data' => array(
'type' => 'from',
'all-day' => !!$all_day_booking ? 'yes' : 'no',
'ajax-load-months' => !!$ajax_load_months ? 'yes' : 'no',
'min-duration' => $product->get_minimum_duration(),
'month-to-load' => $next_month,
'year-to-load' => $next_year,
'min-date' => $min_date,
'max-date' => $max_date,
'not-available-dates' => $not_available_dates ? wp_json_encode($not_available_dates) : '',
'product-id' => $product->get_id(),
'related-to' => '#yith-wcbk-booking-end-date-' . $product->get_id(),
'allow-same-date' => !!$all_day_booking ? 'yes' : 'no',
'allowed-days' => wp_json_encode($product->get_allowed_start_days()),
'on-select-open' => '#yith-wcbk-booking-end-date-' . $product->get_id(),
'static' => 'yes',
'loaded-months' => wp_json_encode($loaded_months),
'months-to-load' => $months_to_load,
),
'value' => $default_start_date,
));
yith_wcbk_print_field(array(
'id' => 'yith-wcbk-booking-hidden-from' . $product->get_id(),
'type' => 'hidden',
'name' => 'from',
'class' => 'yith-wcbk-booking-date yith-wcbk-booking-hidden-from',
'value' => $default_start_date,
));
?>
</div>
</div>
<style>
/* Toggle Button */
.toggle-btn {
transition: background 0.2s, color 0.2s;
}
.toggle-btn.active {
background: #00c48c !important;
color: #fff !important;
}
/* Calendar Containers */
.weekly-calendar,
.monthly-calendar {
margin: 30px 0;
font-family: Arial, sans-serif;
}
/* Table Styling */
.weekly-calendar table,
.monthly-calendar table {
width: 100%;
border-collapse: collapse;
text-align: center;
min-width: 220px;
margin-bottom: 0;
}
/* Table Headers and Cells */
.weekly-calendar th,
.weekly-calendar td,
.monthly-calendar th,
.monthly-calendar td {
border: none;
}
.weekly-calendar th {
text-align: center;
vertical-align: middle;
font-size: 12px;
}
.weekly-calendar td {
font-size: 14px !important;
font-weight: 600 !important;
}
.weekly-calendar th small {
font-size: 10px;
color: #666;
display: block;
margin-top: 2px;
}
.weekly-calendar th,
.monthly-calendar th {
}
.yith-wcbk-booking-form button#prev-week-btn ,.yith-wcbk-booking-form button#next-week-btn{
background: none;
border:none;
cursor: pointer;
}
.yith-wcbk-form-section-dates-wrapper .toggle-btn {
padding: 10px !important;
text-align: center;
font-size: 14px;
font-weight: 600;
}
.weekly-calendar .selected,
.monthly-calendar .selected {
color: #fff;
}
/* Monthly Calendar Layout */
.monthly-calendar {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.prev_month ,.next_month {
background: none;
border: none;
cursor: pointer;
}
.monthly-calendar caption {
font-weight: bold;
margin-bottom: 5px;
}
.monthly-calendar .day-checkbox {
display: none;
}
/* 2. Style the label as a box */
label[for^="day-"] {
display: inline-block;
text-align: center;
font-weight: bold;
position: relative;
cursor: pointer;
}
/* 3. Show tick icon when checked */
.day-checkbox:checked + label[for^="day-"]::after {
content: "✔";
color: green;
font-size: 19px;
position: absolute;
top: -5px;
right: 0px;
background: #ccffcc;
width: 100%;
}
.monthly-calendar td:has(input.unavailable) {
background-color: #ffcccc !important;
border-radius: 5px;
padding: 10px !important;
}
.monthly-calendar td.selected{
background: #00c48c !important;
border-radius: 5px;
padding: 10px !important;
}
.monthly-calendar td:not(:first-child) {
background: #ccffcc;
border-radius: 5px;
}
/* Scroll Container */
.monthly-calendar-scroll {
width: 100%;
overflow-y: auto;
}
.monthly-calendar-scroll.scrollable {
max-height: 350px;
}
/* Calendar Block */
.strek-calendar-block {
margin-bottom: 24px;
background: #fff;
padding: 8px 0 16px 0;
}
.weekly-calendar .strek-calendar-block:last-child {
border-bottom: none;
}
.weekly-calendar td:has(input.unavailable) {
background-color: #ffcccc !important;
}
.weekly-calendar td.selected{
background: #00c48c !important;
}
/* Custom Checkbox */
.weekly-calendar .strek-checkbox {
appearance: none;
-webkit-appearance: none;
width: 20px;
height: 20px;
border: 2px solid #ccc;
border-radius: 4px;
outline: none;
cursor: pointer;
position: relative;
vertical-align: middle; /* default: available (green) */
transition: background 0.2s;
}
.weekly-calendar .strek-checkbox:checked::after {
content: "✔";
display: inline-block;
color: #ffffff;
font-size: 19px;
position: relative;
left: 2px;
top: 0px;
}
.weekly-calendar .week-checkbox.strek-checkbox.unavailable::after {
content: "✖"; /* Unicode cross */
color: red;
font-size: 19px;
position: absolute;
top: 0;
left: 2px;
}
.weekly-calendar .strek-checkbox.unavailable {
background: #ffcccc !important; /* unavailable (red) */
border-color: #ff8888;
border:none;
}
.weekly-calendar .strek-checkbox:disabled {
cursor: not-allowed;
opacity: 0.7;
}
.weekly-calendar .strek-checkbox:checked {
background: #00c48c !important;
border-color: #00c48c;
border:none;
}
/* Weekly calendar specific styles */
.weekly-calendar td {
text-align: center;
vertical-align: middle;
position: relative;
padding: 0px !important;
}
.weekly-calendar td:not(:first-child){
background: #ccffcc;
border-radius: 5px;
}
.weekly-calendar tr{
border:none !important;
}
td small {
font-size: 10px;
line-height: 1.2;
margin-top: 4px;
display: none;
}
.woocommerce div.product form.cart table{
border-collapse: separate;
border-spacing: 12px;
}
</style>
<script>
let displayWeekly='<?php echo $displayWeekly; ?>';
let displayPerday='<?php echo $displayPerday; ?>';
const strekAvail = <?php echo json_encode($strek_avail); ?>;
const strekMonthIdx = {};
Object.keys(strekAvail).forEach(stekId => {
strekMonthIdx[stekId] = 0; // Start at first month for each strek
});
const allowedDays = <?php echo json_encode($product->get_allowed_start_days()); ?>; // 1=Monday, 5=Friday, etc.
console.log('Streak Availability Data:', strekAvail);
console.log('Allowed Days:', allowedDays);
document.addEventListener('DOMContentLoaded', function() {
const weekList = <?php echo json_encode($week_list); ?>;
const weeksToShow = <?php echo $weeks_to_show; ?>;
const loadedMonths = <?php echo json_encode($loaded_months); ?>;
let weekOffset = 0;
let currentMonthIdx = 0;
const notAvailableDates = <?php echo json_encode($not_available_dates); ?>;
// Helper function to check if any day in a week is unavailable
const weekHasUnavailableDates = (weekDates, notAvailableDates) => {
for (let i = 0; i < weekDates.length; i++) {
if (notAvailableDates.includes(weekDates[i])) {
return true;
}
}
return false;
};
// WEEKLY CALENDAR
const renderCalendar = () => {
let html = `<table><thead><tr><th>Stek</th>`;
// Table headers (weeks)
for (let i = 0; i < weeksToShow; i++) {
const weekIdx = weekOffset + i;
if (weekIdx >= weekList.length) break;
const w = weekList[weekIdx];
// Find allowed weekday (e.g. Friday) in this week
let allowedDate = null;
for (let d = 0; d < w.dates.length; d++) {
const dateObj = new Date(w.dates[d]);
// getDay: 0=Sunday, 1=Monday, ..., 5=Friday, 6=Saturday
// allowedDays uses 1=Monday, 7=Sunday (ISO)
// Convert to ISO format: Saturday=6 becomes 6, Sunday=0 becomes 7
const isoDay = dateObj.getDay() === 0 ? 7 : dateObj.getDay();
if (allowedDays.includes(isoDay)) {
allowedDate = w.dates[d];
break;
}
}
let thLabel = `Week ${w.week}`;
if (allowedDate) {
const dateObj = new Date(allowedDate);
const startObj = new Date(w.dates[0]);
const endObj = new Date(w.dates[w.dates.length - 1]);
const startStr = startObj.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' });
const endStr = endObj.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' });
thLabel += `<br><small>${startStr} to ${endStr}</small>`;
}
html += `<th>${thLabel}</th>`;
}
html += `</tr></thead><tbody>`;
// For each stek, create a row
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
html += `<tr><td>${stekData.label}</td>`;
for (let i = 0; i < weeksToShow; i++) {
const weekIdx = weekOffset + i;
if (weekIdx >= weekList.length) break;
const w = weekList[weekIdx];
// Find allowed weekday in this week
let allowedDate = null;
for (let d = 0; d < w.dates.length; d++) {
const dateObj = new Date(w.dates[d]);
// Convert to ISO format: Saturday=6 becomes 6, Sunday=0 becomes 7
const isoDay = dateObj.getDay() === 0 ? 7 : dateObj.getDay();
if (allowedDays.includes(isoDay)) {
allowedDate = w.dates[d];
break;
}
}
// Check if this specific date is available for this specific stek
const isAvailable = allowedDate && stekData.available_dates.includes(allowedDate);
const unavailableClass = isAvailable ? '' : 'unavailable';
// Debug logging for this specific check
if (allowedDate) {
console.log(`Week ${w.week}: ${stekData.label} - Date: ${allowedDate}, Available: ${isAvailable}`);
// Show specific example for August 2nd
if (allowedDate === '2024-08-02') {
console.log(`🎯 August 2nd found! Week: ${w.week}, Resource: ${stekData.label}, Available: ${isAvailable}`);
}
}
html += `<td style="padding: 20px 10px;">
<input type="checkbox" class="week-checkbox strek-checkbox ${unavailableClass}"
id="week-check-${stekId}-${weekIdx}"
data-stek="${stekId}"
data-week="${w.week}"
data-year="${w.year}"
data-date="${allowedDate ? allowedDate : ''}"
${isAvailable ? '' : 'disabled'}>
${allowedDate
? `<br><small>${new Date(allowedDate).toLocaleDateString('nl-NL', { weekday: 'short', day: '2-digit', month: '2-digit' })}</small>`
: '<br><small>n.v.t.</small>'}
${isAvailable ? '<br><small style="color: green;">✓ Available</small>' : '<br><small style="color: red;">✗ Unavailable</small>'}
</td>`;
}
html += `</tr>`;
});
html += `</tbody></table>`;
document.getElementById('calendar-table-wrapper').innerHTML = html;
// Update navigation label
const navLabel = document.getElementById('week-navigation-label');
if (navLabel && weekList.length > 0) {
const firstWeek = weekList[weekOffset];
const lastWeek = weekList[Math.min(weekOffset + weeksToShow - 1, weekList.length - 1)];
if (firstWeek && lastWeek) {
const firstDate = new Date(firstWeek.start);
const lastDate = new Date(lastWeek.start);
lastDate.setDate(lastDate.getDate() + 6);
navLabel.textContent = `Weeks ${firstWeek.week} - ${lastWeek.week} (${firstDate.toLocaleDateString('nl-NL')} - ${lastDate.toLocaleDateString('nl-NL')})`;
}
}
// Selection highlight for checkboxes and event handlers
document.querySelectorAll('.weekly-calendar .week-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', () => {
const td = checkbox.closest('td');
if (checkbox.checked) {
// Uncheck all other week checkboxes across all streks
document.querySelectorAll('.weekly-calendar .week-checkbox').forEach(cb => {
if (cb !== checkbox) {
cb.checked = false;
cb.closest('td').classList.remove('selected');
}
});
td.classList.add('selected');
// Update the booking form fields
const strekId = checkbox.getAttribute('data-stek');
const productId = "<?php echo $product->get_id(); ?>";
const selectedDate = checkbox.getAttribute('data-date');
// Set the check-in (start date) field
const startDateInput = document.getElementById(`yith-wcbk-booking-start-date-${productId}`);
if (startDateInput) {
startDateInput.value = selectedDate;
startDateInput.dispatchEvent(new Event('input', { bubbles: true }));
startDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(startDateInput).datepicker) {
jQuery(startDateInput).datepicker('setDate', selectedDate);
}
}
// Set the check-out (end date) field to 7 days after check-in
const endDateInput = document.getElementById(`yith-wcbk-booking-end-date-${productId}`);
if (endDateInput) {
const dateObj = new Date(selectedDate);
dateObj.setDate(dateObj.getDate() + 7); // 7 days (week)
const yyyy = dateObj.getFullYear();
const mm = String(dateObj.getMonth() + 1).padStart(2, '0');
const dd = String(dateObj.getDate()).padStart(2, '0');
const endDate = `${yyyy}-${mm}-${dd}`;
endDateInput.value = endDate;
endDateInput.dispatchEvent(new Event('input', { bubbles: true }));
endDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(endDateInput).datepicker) {
jQuery(endDateInput).datepicker('setDate', endDate);
}
}
// Update strek dropdown
const select = document.querySelector(`#yith-wcbk-booking-resources-${productId} select.yith-wcbk-selector__field`);
if (select) {
select.value = strekId;
const event = new Event('change', { bubbles: true });
select.dispatchEvent(event);
const customItem = document.querySelector(`#yith-wcbk-booking-resources-${productId} .yith-wcbk-selector__item[data-key="${strekId}"]`);
if (customItem) {
customItem.click();
}
}
} else {
td.classList.remove('selected');
}
});
});
};
document.getElementById('prev-week-btn')?.remove();
document.getElementById('next-week-btn')?.remove();
const navDiv = document.createElement('div');
navDiv.style.display = "flex";
navDiv.style.justifyContent = "center";
navDiv.style.marginBottom = "10px";
navDiv.innerHTML = `
<button type="button" id="prev-week-btn" style="display:<?php echo $displayWeekly; ?>;"> <img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/left-chevron-svgrepo-com.svg" alt="Previous" width="25" height="25"> </button>
<div style="flex: 1; text-align: center; font-weight: bold;display:<?php echo $displayWeekly; ?>;" id="week-navigation-label">Select a week</div>
<button type="button" id="next-week-btn" style="display:<?php echo $displayWeekly; ?>;"> <img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/right-chevron-svgrepo-com.svg" alt="Previous" width="25" height="25"> </button>
`;
document.getElementById('calendar-table-wrapper').before(navDiv);
document.getElementById('prev-week-btn').addEventListener('click', () => {
if (weekOffset - weeksToShow >= 0) {
weekOffset -= weeksToShow;
} else {
weekOffset = 0;
}
renderCalendar();
});
document.getElementById('next-week-btn').addEventListener('click', () => {
if (weekOffset + weeksToShow < weekList.length) {
weekOffset += weeksToShow;
renderCalendar();
}
});
// MONTHLY CALENDAR (one month at a time)
const renderMonthlyCalendar = () => {
if (loadedMonths.length === 0) {
document.getElementById('monthly-calendar-scroll').innerHTML = '<p>No months available.</p>';
document.getElementById('monthly-calendar-controls').style.display = 'none';
return;
}
document.getElementById('monthly-calendar-controls').style.display = 'none'; // Hide global controls
let html = '';
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
html += renderStrekMonthBlock(stekId, stekData);
});
document.getElementById('monthly-calendar-scroll').innerHTML = html;
// Add event listeners for each strek's navigation
Object.keys(strekAvail).forEach(stekId => {
document.getElementById(`prev-month-btn-${stekId}`).addEventListener('click', () => {
if (strekMonthIdx[stekId] > 0) {
strekMonthIdx[stekId]--;
renderMonthlyCalendar();
}
});
document.getElementById(`next-month-btn-${stekId}`).addEventListener('click', () => {
if (strekMonthIdx[stekId] < loadedMonths.length - 1) {
strekMonthIdx[stekId]++;
renderMonthlyCalendar();
}
});
});
// Add scrollable class if needed
const strekCount = Object.keys(strekAvail).length;
const scrollDiv = document.getElementById('monthly-calendar-scroll');
if (strekCount > 2) {
scrollDiv.classList.add('scrollable');
} else {
scrollDiv.classList.remove('scrollable');
}
};
const renderStrekMonthBlock = (stekId, stekData) => {
const idx = strekMonthIdx[stekId];
const [year, month] = loadedMonths[idx].split('-').map(Number);
const availableDates = Array.isArray(stekData.available_dates) ? stekData.available_dates : [];
// Get first and last day of month
const firstDay = new Date(year, month - 1, 1);
const lastDay = new Date(year, month, 0);
const daysInMonth = lastDay.getDate();
let html = `<div class="strek-calendar-block">
<div style="font-weight:bold; margin-bottom:20px;">${stekData.label}</div>
<div style="text-align:center; margin-bottom:10px; display:flex; align-items: center; justify-content: space-between;">
<button type="button" id="prev-month-btn-${stekId}" class="prev_month"><img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/left-chevron-svgrepo-com.svg" alt="Next" width="25" height="25"> </button>
<span style="font-weight:bold; margin:0 10px;">${firstDay.toLocaleString('default', { month: 'long', year: 'numeric' })}</span>
<button type="button" id="next-month-btn-${stekId}" class="next_month"><img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/right-chevron-svgrepo-com.svg" alt="Next" width="25" height="25"></button>
</div>
<table>
<thead>
<tr>
<th>Sa</th><th>Su</th><th>Mo</th><th>Tu</th><th>We</th><th>Th</th><th>Fr</th>
</tr>
</thead>
<tbody>
<tr>`;
// Calculate the day of week for the 1st (Saturday=0)
let dayOfWeek = (firstDay.getDay() + 1) % 7;
for (let i = 0; i < dayOfWeek; i++) html += '<td></td>';
for (let day = 1; day <= daysInMonth; day++) {
if ((dayOfWeek % 7 === 0) && day !== 1) html += '</tr><tr>';
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
const isAvailable = availableDates.includes(dateStr);
const unavailableClass = isAvailable ? '' : 'unavailable';
const disabledAttr = isAvailable ? '' : 'disabled';
html += `<td>
<input type="checkbox" class="day-checkbox strek-checkbox ${unavailableClass}"
id="day-${stekId}-${year}-${month}-${day}"
data-date="${dateStr}"
data-stek="${stekId}"
${disabledAttr}>
<label for="day-${stekId}-${year}-${month}-${day}">${day}</label>
</td>`;
dayOfWeek++;
}
while (dayOfWeek % 7 !== 0) { html += '<td></td>'; dayOfWeek++; }
html += '</tr></tbody></table></div>';
return html;
};
const renderSingleMonth = (year, month) => {
// For each strek, render a classic calendar
let html = '';
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
// Get first and last day of month
const firstDay = new Date(year, month - 1, 1);
const lastDay = new Date(year, month, 0);
const daysInMonth = lastDay.getDate();
html += `<div class="strek-calendar-block">
<div style="font-weight:bold; margin-bottom:4px;">${stekData.label}</div>
<table>
<caption>${firstDay.toLocaleString('default', { month: 'long' })} ${year}</caption>
<thead>
<tr>
<th>Sa</th><th>Su</th><th>Mo</th><th>Tu</th><th>We</th><th>Th</th><th>Fr</th>
</tr>
</thead>
<tbody>
<tr>`;
// Calculate the day of week for the 1st (Saturday=0)
let dayOfWeek = (firstDay.getDay() + 1) % 7;
for (let i = 0; i < dayOfWeek; i++) html += '<td></td>';
for (let day = 1; day <= daysInMonth; day++) {
if ((dayOfWeek % 7 === 0) && day !== 1) html += '</tr><tr>';
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
html += `<td>
<input type="checkbox" class="day-checkbox strek-checkbox" id="day-${stekId}-${year}-${month}-${day}" data-date="${dateStr}" data-stek="${stekId}">
<label for="day-${stekId}-${year}-${month}-${day}">${day}</label>
</td>`;
dayOfWeek++;
}
while (dayOfWeek % 7 !== 0) { html += '<td></td>'; dayOfWeek++; }
html += '</tr></tbody></table></div>';
});
return html;
};
// TOGGLE LOGIC
document.getElementById('toggle-week').addEventListener('click', function() {
document.getElementById('toggle-week').classList.add('active');
document.getElementById('toggle-day').classList.remove('active');
document.getElementById('calendar-table-wrapper').style.display = '';
document.getElementById('monthly-calendar-wrapper').style.display = 'none';
document.getElementById('monthly-calendar-controls').style.display = 'none';
navDiv.style.display = '';
renderCalendar();
});
document.getElementById('toggle-day').addEventListener('click', function() {
document.getElementById('toggle-day').classList.add('active');
document.getElementById('toggle-week').classList.remove('active');
document.getElementById('calendar-table-wrapper').style.display = 'none';
document.getElementById('monthly-calendar-wrapper').style.display = '';
document.getElementById('monthly-calendar-controls').style.display = '';
navDiv.style.display = 'none';
renderMonthlyCalendar();
});
// Initial render
if(displayWeekly=='block'){
renderCalendar();
}else{
renderMonthlyCalendar();
}
document.getElementById('prev-month-btn').addEventListener('click', () => {
if (currentMonthIdx > 0) {
currentMonthIdx--;
renderMonthlyCalendar();
}
});
document.getElementById('next-month-btn').addEventListener('click', () => {
if (currentMonthIdx < loadedMonths.length - 1) {
currentMonthIdx++;
renderMonthlyCalendar();
}
});
document.addEventListener('change', function(e) {
if (
e.target.classList.contains('day-checkbox') &&
e.target.checked &&
!e.target.disabled
) {
// Uncheck all other day-checkboxes
document.querySelectorAll('.day-checkbox').forEach(cb => {
if (cb !== e.target) cb.checked = false;
});
const strekId = e.target.getAttribute('data-stek');
const productId = "<?php echo $product->get_id(); ?>";
const selectedDate = e.target.getAttribute('data-date');
// Set the check-in (start date) field and fire change event
const startDateInput = document.getElementById(`yith-wcbk-booking-start-date-${productId}`);
startDateInput.value = selectedDate;
// Fire both input and change events
startDateInput.dispatchEvent(new Event('input', { bubbles: true }));
startDateInput.dispatchEvent(new Event('change', { bubbles: true }));
// If a jQuery datepicker is attached, update it as well
if (typeof jQuery !== 'undefined' && jQuery(startDateInput).datepicker) {
jQuery(startDateInput).datepicker('setDate', selectedDate);
}
// Set the check-out (end date) field to one day after check-in
const endDateInput = document.getElementById(`yith-wcbk-booking-end-date-${productId}`);
if (endDateInput) {
// Calculate next day
const dateObj = new Date(selectedDate);
dateObj.setDate(dateObj.getDate() + 1);
// Format as yyyy-mm-dd
const yyyy = dateObj.getFullYear();
const mm = String(dateObj.getMonth() + 1).padStart(2, '0');
const dd = String(dateObj.getDate()).padStart(2, '0');
const nextDay = `${yyyy}-${mm}-${dd}`;
endDateInput.value = nextDay;
endDateInput.dispatchEvent(new Event('input', { bubbles: true }));
endDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(endDateInput).datepicker) {
jQuery(endDateInput).datepicker('setDate', nextDay);
}
}
// Strek dropdown logic (as before)
const select = document.querySelector(`#yith-wcbk-booking-resources-${productId} select.yith-wcbk-selector__field`);
if (select) {
select.value = strekId;
const event = new Event('change', { bubbles: true });
select.dispatchEvent(event);
const customItem = document.querySelector(`#yith-wcbk-booking-resources-${productId} .yith-wcbk-selector__item[data-key="${strekId}"]`);
if (customItem) {
customItem.click();
}
}
}
});
});
</script>
<style>
/* Toggle Button */
.toggle-btn {
transition: background 0.2s, color 0.2s;
}
.toggle-btn.active {
background: #00c48c !important;
color: #fff !important;
}
/* Calendar Containers */
.weekly-calendar,
.monthly-calendar {
margin: 30px 0;
font-family: Arial, sans-serif;
}
/* Table Styling */
.weekly-calendar table,
.monthly-calendar table {
width: 100%;
border-collapse: collapse;
text-align: center;
min-width: 220px;
margin-bottom: 0;
}
/* Table Headers and Cells */
.weekly-calendar th,
.weekly-calendar td,
.monthly-calendar th,
.monthly-calendar td {
border: none;
}
.weekly-calendar th {
text-align: center;
vertical-align: middle;
font-size: 12px;
}
.weekly-calendar td {
font-size: 14px !important;
font-weight: 600 !important;
}
.weekly-calendar th small {
font-size: 10px;
color: #666;
display: block;
margin-top: 2px;
}
.weekly-calendar th,
.monthly-calendar th {
}
.yith-wcbk-booking-form button#prev-week-btn ,.yith-wcbk-booking-form button#next-week-btn{
background: none;
border:none;
cursor: pointer;
}
.yith-wcbk-form-section-dates-wrapper .toggle-btn {
padding: 10px !important;
text-align: center;
font-size: 14px;
font-weight: 600;
}
.weekly-calendar .selected,
.monthly-calendar .selected {
color: #fff;
}
/* Monthly Calendar Layout */
.monthly-calendar {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.prev_month ,.next_month {
background: none;
border: none;
cursor: pointer;
}
.monthly-calendar caption {
font-weight: bold;
margin-bottom: 5px;
}
.monthly-calendar .day-checkbox {
display: none;
}
/* 2. Style the label as a box */
label[for^="day-"] {
display: inline-block;
text-align: center;
font-weight: bold;
position: relative;
cursor: pointer;
}
/* 3. Show tick icon when checked */
.day-checkbox:checked + label[for^="day-"]::after {
content: "✔";
color: green;
font-size: 19px;
position: absolute;
top: -5px;
right: 0px;
background: #ccffcc;
width: 100%;
}
.monthly-calendar td:has(input.unavailable) {
background-color: #ffcccc !important;
border-radius: 5px;
padding: 10px !important;
}
.monthly-calendar td.selected{
background: #00c48c !important;
border-radius: 5px;
padding: 10px !important;
}
.monthly-calendar td:not(:first-child) {
background: #ccffcc;
border-radius: 5px;
}
/* Scroll Container */
.monthly-calendar-scroll {
width: 100%;
overflow-y: auto;
}
.monthly-calendar-scroll.scrollable {
max-height: 350px;
}
/* Calendar Block */
.strek-calendar-block {
margin-bottom: 24px;
background: #fff;
padding: 8px 0 16px 0;
}
.weekly-calendar .strek-calendar-block:last-child {
border-bottom: none;
}
.weekly-calendar td:has(input.unavailable) {
background-color: #ffcccc !important;
}
.weekly-calendar td.selected{
background: #00c48c !important;
}
/* Custom Checkbox */
.weekly-calendar .strek-checkbox {
appearance: none;
-webkit-appearance: none;
width: 20px;
height: 20px;
border: 2px solid #ccc;
border-radius: 4px;
outline: none;
cursor: pointer;
position: relative;
vertical-align: middle; /* default: available (green) */
transition: background 0.2s;
}
.weekly-calendar .strek-checkbox:checked::after {
content: "✔";
display: inline-block;
color: #ffffff;
font-size: 19px;
position: relative;
left: 2px;
top: 0px;
}
.weekly-calendar .week-checkbox.strek-checkbox.unavailable::after {
content: "✖"; /* Unicode cross */
color: red;
font-size: 19px;
position: absolute;
top: 0;
left: 2px;
}
.weekly-calendar .strek-checkbox.unavailable {
background: #ffcccc !important; /* unavailable (red) */
border-color: #ff8888;
border:none;
}
.weekly-calendar .strek-checkbox:disabled {
cursor: not-allowed;
opacity: 0.7;
}
.weekly-calendar .strek-checkbox:checked {
background: #00c48c !important;
border-color: #00c48c;
border:none;
}
/* Weekly calendar specific styles */
.weekly-calendar td {
text-align: center;
vertical-align: middle;
position: relative;
padding: 0px !important;
}
.weekly-calendar td:not(:first-child){
background: #ccffcc;
border-radius: 5px;
}
.weekly-calendar tr{
border:none !important;
}
td small {
font-size: 10px;
line-height: 1.2;
margin-top: 4px;
display: none;
}
.woocommerce div.product form.cart table{
border-collapse: separate;
border-spacing: 12px;
}
</style>
<script>
let displayWeekly='<?php echo $displayWeekly; ?>';
let displayPerday='<?php echo $displayPerday; ?>';
const strekAvail = <?php echo json_encode($strek_avail); ?>;
const strekMonthIdx = {};
Object.keys(strekAvail).forEach(stekId => {
strekMonthIdx[stekId] = 0; // Start at first month for each strek
});
const allowedDays = <?php echo json_encode($product->get_allowed_start_days()); ?>; // 1=Monday, 5=Friday, etc.
console.log('Streak Availability Data:', strekAvail);
console.log('Allowed Days:', allowedDays);
document.addEventListener('DOMContentLoaded', function() {
const weekList = <?php echo json_encode($week_list); ?>;
const weeksToShow = <?php echo $weeks_to_show; ?>;
const loadedMonths = <?php echo json_encode($loaded_months); ?>;
let weekOffset = 0;
let currentMonthIdx = 0;
const notAvailableDates = <?php echo json_encode($not_available_dates); ?>;
// Helper function to check if any day in a week is unavailable
const weekHasUnavailableDates = (weekDates, notAvailableDates) => {
for (let i = 0; i < weekDates.length; i++) {
if (notAvailableDates.includes(weekDates[i])) {
return true;
}
}
return false;
};
// WEEKLY CALENDAR
const renderCalendar = () => {
let html = `<table><thead><tr><th>Stek</th>`;
// Table headers (weeks)
for (let i = 0; i < weeksToShow; i++) {
const weekIdx = weekOffset + i;
if (weekIdx >= weekList.length) break;
const w = weekList[weekIdx];
// Find allowed weekday (e.g. Friday) in this week
let allowedDate = null;
for (let d = 0; d < w.dates.length; d++) {
const dateObj = new Date(w.dates[d]);
// getDay: 0=Sunday, 1=Monday, ..., 5=Friday, 6=Saturday
// allowedDays uses 1=Monday, 7=Sunday (ISO)
// Convert to ISO format: Saturday=6 becomes 6, Sunday=0 becomes 7
const isoDay = dateObj.getDay() === 0 ? 7 : dateObj.getDay();
if (allowedDays.includes(isoDay)) {
allowedDate = w.dates[d];
break;
}
}
let thLabel = `Week ${w.week}`;
if (allowedDate) {
const dateObj = new Date(allowedDate);
const startObj = new Date(w.dates[0]);
const endObj = new Date(w.dates[w.dates.length - 1]);
const startStr = startObj.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' });
const endStr = endObj.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' });
thLabel += `<br><small>${startStr} to ${endStr}</small>`;
}
html += `<th>${thLabel}</th>`;
}
html += `</tr></thead><tbody>`;
// For each stek, create a row
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
html += `<tr><td>${stekData.label}</td>`;
for (let i = 0; i < weeksToShow; i++) {
const weekIdx = weekOffset + i;
if (weekIdx >= weekList.length) break;
const w = weekList[weekIdx];
// Find allowed weekday in this week
let allowedDate = null;
for (let d = 0; d < w.dates.length; d++) {
const dateObj = new Date(w.dates[d]);
// Convert to ISO format: Saturday=6 becomes 6, Sunday=0 becomes 7
const isoDay = dateObj.getDay() === 0 ? 7 : dateObj.getDay();
if (allowedDays.includes(isoDay)) {
allowedDate = w.dates[d];
break;
}
}
// Check if this specific date is available for this specific stek
const isAvailable = allowedDate && stekData.available_dates.includes(allowedDate);
const unavailableClass = isAvailable ? '' : 'unavailable';
// Debug logging for this specific check
if (allowedDate) {
console.log(`Week ${w.week}: ${stekData.label} - Date: ${allowedDate}, Available: ${isAvailable}`);
// Show specific example for August 2nd
if (allowedDate === '2024-08-02') {
console.log(`🎯 August 2nd found! Week: ${w.week}, Resource: ${stekData.label}, Available: ${isAvailable}`);
}
}
html += `<td style="padding: 20px 10px;">
<input type="checkbox" class="week-checkbox strek-checkbox ${unavailableClass}"
id="week-check-${stekId}-${weekIdx}"
data-stek="${stekId}"
data-week="${w.week}"
data-year="${w.year}"
data-date="${allowedDate ? allowedDate : ''}"
${isAvailable ? '' : 'disabled'}>
${allowedDate
? `<br><small>${new Date(allowedDate).toLocaleDateString('nl-NL', { weekday: 'short', day: '2-digit', month: '2-digit' })}</small>`
: '<br><small>n.v.t.</small>'}
${isAvailable ? '<br><small style="color: green;">✓ Available</small>' : '<br><small style="color: red;">✗ Unavailable</small>'}
</td>`;
}
html += `</tr>`;
});
html += `</tbody></table>`;
document.getElementById('calendar-table-wrapper').innerHTML = html;
// Update navigation label
const navLabel = document.getElementById('week-navigation-label');
if (navLabel && weekList.length > 0) {
const firstWeek = weekList[weekOffset];
const lastWeek = weekList[Math.min(weekOffset + weeksToShow - 1, weekList.length - 1)];
if (firstWeek && lastWeek) {
const firstDate = new Date(firstWeek.start);
const lastDate = new Date(lastWeek.start);
lastDate.setDate(lastDate.getDate() + 6);
navLabel.textContent = `Weeks ${firstWeek.week} - ${lastWeek.week} (${firstDate.toLocaleDateString('nl-NL')} - ${lastDate.toLocaleDateString('nl-NL')})`;
}
}
// Selection highlight for checkboxes and event handlers
document.querySelectorAll('.weekly-calendar .week-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', () => {
const td = checkbox.closest('td');
if (checkbox.checked) {
// Uncheck all other week checkboxes across all streks
document.querySelectorAll('.weekly-calendar .week-checkbox').forEach(cb => {
if (cb !== checkbox) {
cb.checked = false;
cb.closest('td').classList.remove('selected');
}
});
td.classList.add('selected');
// Update the booking form fields
const strekId = checkbox.getAttribute('data-stek');
const productId = "<?php echo $product->get_id(); ?>";
const selectedDate = checkbox.getAttribute('data-date');
// Set the check-in (start date) field
const startDateInput = document.getElementById(`yith-wcbk-booking-start-date-${productId}`);
if (startDateInput) {
startDateInput.value = selectedDate;
startDateInput.dispatchEvent(new Event('input', { bubbles: true }));
startDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(startDateInput).datepicker) {
jQuery(startDateInput).datepicker('setDate', selectedDate);
}
}
// Set the check-out (end date) field to 7 days after check-in
const endDateInput = document.getElementById(`yith-wcbk-booking-end-date-${productId}`);
if (endDateInput) {
const dateObj = new Date(selectedDate);
dateObj.setDate(dateObj.getDate() + 7); // 7 days (week)
const yyyy = dateObj.getFullYear();
const mm = String(dateObj.getMonth() + 1).padStart(2, '0');
const dd = String(dateObj.getDate()).padStart(2, '0');
const endDate = `${yyyy}-${mm}-${dd}`;
endDateInput.value = endDate;
endDateInput.dispatchEvent(new Event('input', { bubbles: true }));
endDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(endDateInput).datepicker) {
jQuery(endDateInput).datepicker('setDate', endDate);
}
}
// Update strek dropdown
const select = document.querySelector(`#yith-wcbk-booking-resources-${productId} select.yith-wcbk-selector__field`);
if (select) {
select.value = strekId;
const event = new Event('change', { bubbles: true });
select.dispatchEvent(event);
const customItem = document.querySelector(`#yith-wcbk-booking-resources-${productId} .yith-wcbk-selector__item[data-key="${strekId}"]`);
if (customItem) {
customItem.click();
}
}
} else {
td.classList.remove('selected');
}
});
});
};
document.getElementById('prev-week-btn')?.remove();
document.getElementById('next-week-btn')?.remove();
const navDiv = document.createElement('div');
navDiv.style.display = "flex";
navDiv.style.justifyContent = "center";
navDiv.style.marginBottom = "10px";
navDiv.innerHTML = `
<button type="button" id="prev-week-btn" style="display:<?php echo $displayWeekly; ?>;"> <img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/left-chevron-svgrepo-com.svg" alt="Previous" width="25" height="25"> </button>
<div style="flex: 1; text-align: center; font-weight: bold;display:<?php echo $displayWeekly; ?>;" id="week-navigation-label">Select a week</div>
<button type="button" id="next-week-btn" style="display:<?php echo $displayWeekly; ?>;"> <img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/right-chevron-svgrepo-com.svg" alt="Previous" width="25" height="25"> </button>
`;
document.getElementById('calendar-table-wrapper').before(navDiv);
document.getElementById('prev-week-btn').addEventListener('click', () => {
if (weekOffset - weeksToShow >= 0) {
weekOffset -= weeksToShow;
} else {
weekOffset = 0;
}
renderCalendar();
});
document.getElementById('next-week-btn').addEventListener('click', () => {
if (weekOffset + weeksToShow < weekList.length) {
weekOffset += weeksToShow;
renderCalendar();
}
});
// MONTHLY CALENDAR (one month at a time)
const renderMonthlyCalendar = () => {
if (loadedMonths.length === 0) {
document.getElementById('monthly-calendar-scroll').innerHTML = '<p>No months available.</p>';
document.getElementById('monthly-calendar-controls').style.display = 'none';
return;
}
document.getElementById('monthly-calendar-controls').style.display = 'none'; // Hide global controls
let html = '';
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
html += renderStrekMonthBlock(stekId, stekData);
});
document.getElementById('monthly-calendar-scroll').innerHTML = html;
// Add event listeners for each strek's navigation
Object.keys(strekAvail).forEach(stekId => {
document.getElementById(`prev-month-btn-${stekId}`).addEventListener('click', () => {
if (strekMonthIdx[stekId] > 0) {
strekMonthIdx[stekId]--;
renderMonthlyCalendar();
}
});
document.getElementById(`next-month-btn-${stekId}`).addEventListener('click', () => {
if (strekMonthIdx[stekId] < loadedMonths.length - 1) {
strekMonthIdx[stekId]++;
renderMonthlyCalendar();
}
});
});
// Add scrollable class if needed
const strekCount = Object.keys(strekAvail).length;
const scrollDiv = document.getElementById('monthly-calendar-scroll');
if (strekCount > 2) {
scrollDiv.classList.add('scrollable');
} else {
scrollDiv.classList.remove('scrollable');
}
};
const renderStrekMonthBlock = (stekId, stekData) => {
const idx = strekMonthIdx[stekId];
const [year, month] = loadedMonths[idx].split('-').map(Number);
const availableDates = Array.isArray(stekData.available_dates) ? stekData.available_dates : [];
// Get first and last day of month
const firstDay = new Date(year, month - 1, 1);
const lastDay = new Date(year, month, 0);
const daysInMonth = lastDay.getDate();
let html = `<div class="strek-calendar-block">
<div style="font-weight:bold; margin-bottom:20px;">${stekData.label}</div>
<div style="text-align:center; margin-bottom:10px; display:flex; align-items: center; justify-content: space-between;">
<button type="button" id="prev-month-btn-${stekId}" class="prev_month"><img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/left-chevron-svgrepo-com.svg" alt="Next" width="25" height="25"> </button>
<span style="font-weight:bold; margin:0 10px;">${firstDay.toLocaleString('default', { month: 'long', year: 'numeric' })}</span>
<button type="button" id="next-month-btn-${stekId}" class="next_month"><img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/right-chevron-svgrepo-com.svg" alt="Next" width="25" height="25"></button>
</div>
<table>
<thead>
<tr>
<th>Sa</th><th>Su</th><th>Mo</th><th>Tu</th><th>We</th><th>Th</th><th>Fr</th>
</tr>
</thead>
<tbody>
<tr>`;
// Calculate the day of week for the 1st (Saturday=0)
let dayOfWeek = (firstDay.getDay() + 1) % 7;
for (let i = 0; i < dayOfWeek; i++) html += '<td></td>';
for (let day = 1; day <= daysInMonth; day++) {
if ((dayOfWeek % 7 === 0) && day !== 1) html += '</tr><tr>';
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
const isAvailable = availableDates.includes(dateStr);
const unavailableClass = isAvailable ? '' : 'unavailable';
const disabledAttr = isAvailable ? '' : 'disabled';
html += `<td>
<input type="checkbox" class="day-checkbox strek-checkbox ${unavailableClass}"
id="day-${stekId}-${year}-${month}-${day}"
data-date="${dateStr}"
data-stek="${stekId}"
${disabledAttr}>
<label for="day-${stekId}-${year}-${month}-${day}">${day}</label>
</td>`;
dayOfWeek++;
}
while (dayOfWeek % 7 !== 0) { html += '<td></td>'; dayOfWeek++; }
html += '</tr></tbody></table></div>';
return html;
};
const renderSingleMonth = (year, month) => {
// For each strek, render a classic calendar
let html = '';
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
// Get first and last day of month
const firstDay = new Date(year, month - 1, 1);
const lastDay = new Date(year, month, 0);
const daysInMonth = lastDay.getDate();
html += `<div class="strek-calendar-block">
<div style="font-weight:bold; margin-bottom:4px;">${stekData.label}</div>
<table>
<caption>${firstDay.toLocaleString('default', { month: 'long' })} ${year}</caption>
<thead>
<tr>
<th>Sa</th><th>Su</th><th>Mo</th><th>Tu</th><th>We</th><th>Th</th><th>Fr</th>
</tr>
</thead>
<tbody>
<tr>`;
// Calculate the day of week for the 1st (Saturday=0)
let dayOfWeek = (firstDay.getDay() + 1) % 7;
for (let i = 0; i < dayOfWeek; i++) html += '<td></td>';
for (let day = 1; day <= daysInMonth; day++) {
if ((dayOfWeek % 7 === 0) && day !== 1) html += '</tr><tr>';
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
html += `<td>
<input type="checkbox" class="day-checkbox strek-checkbox" id="day-${stekId}-${year}-${month}-${day}" data-date="${dateStr}" data-stek="${stekId}">
<label for="day-${stekId}-${year}-${month}-${day}">${day}</label>
</td>`;
dayOfWeek++;
}
while (dayOfWeek % 7 !== 0) { html += '<td></td>'; dayOfWeek++; }
html += '</tr></tbody></table></div>';
});
return html;
};
// TOGGLE LOGIC
document.getElementById('toggle-week').addEventListener('click', function() {
document.getElementById('toggle-week').classList.add('active');
document.getElementById('toggle-day').classList.remove('active');
document.getElementById('calendar-table-wrapper').style.display = '';
document.getElementById('monthly-calendar-wrapper').style.display = 'none';
document.getElementById('monthly-calendar-controls').style.display = 'none';
navDiv.style.display = '';
renderCalendar();
});
document.getElementById('toggle-day').addEventListener('click', function() {
document.getElementById('toggle-day').classList.add('active');
document.getElementById('toggle-week').classList.remove('active');
document.getElementById('calendar-table-wrapper').style.display = 'none';
document.getElementById('monthly-calendar-wrapper').style.display = '';
document.getElementById('monthly-calendar-controls').style.display = '';
navDiv.style.display = 'none';
renderMonthlyCalendar();
});
// Initial render
if(displayWeekly=='block'){
renderCalendar();
}else{
renderMonthlyCalendar();
}
document.getElementById('prev-month-btn').addEventListener('click', () => {
if (currentMonthIdx > 0) {
currentMonthIdx--;
renderMonthlyCalendar();
}
});
document.getElementById('next-month-btn').addEventListener('click', () => {
if (currentMonthIdx < loadedMonths.length - 1) {
currentMonthIdx++;
renderMonthlyCalendar();
}
});
document.addEventListener('change', function(e) {
if (
e.target.classList.contains('day-checkbox') &&
e.target.checked &&
!e.target.disabled
) {
// Uncheck all other day-checkboxes
document.querySelectorAll('.day-checkbox').forEach(cb => {
if (cb !== e.target) cb.checked = false;
});
const strekId = e.target.getAttribute('data-stek');
const productId = "<?php echo $product->get_id(); ?>";
const selectedDate = e.target.getAttribute('data-date');
// Set the check-in (start date) field and fire change event
const startDateInput = document.getElementById(`yith-wcbk-booking-start-date-${productId}`);
startDateInput.value = selectedDate;
// Fire both input and change events
startDateInput.dispatchEvent(new Event('input', { bubbles: true }));
startDateInput.dispatchEvent(new Event('change', { bubbles: true }));
// If a jQuery datepicker is attached, update it as well
if (typeof jQuery !== 'undefined' && jQuery(startDateInput).datepicker) {
jQuery(startDateInput).datepicker('setDate', selectedDate);
}
// Set the check-out (end date) field to one day after check-in
const endDateInput = document.getElementById(`yith-wcbk-booking-end-date-${productId}`);
if (endDateInput) {
// Calculate next day
const dateObj = new Date(selectedDate);
dateObj.setDate(dateObj.getDate() + 1);
// Format as yyyy-mm-dd
const yyyy = dateObj.getFullYear();
const mm = String(dateObj.getMonth() + 1).padStart(2, '0');
const dd = String(dateObj.getDate()).padStart(2, '0');
const nextDay = `${yyyy}-${mm}-${dd}`;
endDateInput.value = nextDay;
endDateInput.dispatchEvent(new Event('input', { bubbles: true }));
endDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(endDateInput).datepicker) {
jQuery(endDateInput).datepicker('setDate', nextDay);
}
}
// Strek dropdown logic (as before)
const select = document.querySelector(`#yith-wcbk-booking-resources-${productId} select.yith-wcbk-selector__field`);
if (select) {
select.value = strekId;
const event = new Event('change', { bubbles: true });
select.dispatchEvent(event);
const customItem = document.querySelector(`#yith-wcbk-booking-resources-${productId} .yith-wcbk-selector__item[data-key="${strekId}"]`);
if (customItem) {
customItem.click();
}
}
}
});
});
</script>
flex-wrap: wrap;
gap: 10px;
}
.prev_month ,.next_month {
background: none;
border: none;
cursor: pointer;
}
.monthly-calendar caption {
font-weight: bold;
margin-bottom: 5px;
}
.monthly-calendar .day-checkbox {
display: none;
}
/* 2. Style the label as a box */
label[for^="day-"] {
display: inline-block;
text-align: center;
font-weight: bold;
position: relative;
cursor: pointer;
}
/* 3. Show tick icon when checked */
.day-checkbox:checked + label[for^="day-"]::after {
content: "✔";
color: green;
font-size: 19px;
position: absolute;
top: -5px;
right: 0px;
background: #ccffcc;
width: 100%;
}
.monthly-calendar td:has(input.unavailable) {
background-color: #ffcccc !important;
border-radius: 5px;
padding: 10px !important;
}
.monthly-calendar td.selected{
background: #00c48c !important;
border-radius: 5px;
padding: 10px !important;
}
.monthly-calendar td:not(:first-child) {
background: #ccffcc;
border-radius: 5px;
}
/* Scroll Container */
.monthly-calendar-scroll {
width: 100%;
overflow-y: auto;
}
.monthly-calendar-scroll.scrollable {
max-height: 350px;
}
/* Calendar Block */
.strek-calendar-block {
margin-bottom: 24px;
background: #fff;
padding: 8px 0 16px 0;
}
.weekly-calendar .strek-calendar-block:last-child {
border-bottom: none;
}
.weekly-calendar td:has(input.unavailable) {
background-color: #ffcccc !important;
}
.weekly-calendar td.selected{
background: #00c48c !important;
}
/* Custom Checkbox */
.weekly-calendar .strek-checkbox {
appearance: none;
-webkit-appearance: none;
width: 20px;
height: 20px;
border: 2px solid #ccc;
border-radius: 4px;
outline: none;
cursor: pointer;
position: relative;
vertical-align: middle; /* default: available (green) */
transition: background 0.2s;
}
.weekly-calendar .strek-checkbox:checked::after {
content: "✔";
display: inline-block;
color: #ffffff;
font-size: 19px;
position: relative;
left: 2px;
top: 0px;
}
.weekly-calendar .week-checkbox.strek-checkbox.unavailable::after {
content: "✖"; /* Unicode cross */
color: red;
font-size: 19px;
position: absolute;
top: 0;
left: 2px;
}
.weekly-calendar .strek-checkbox.unavailable {
background: #ffcccc !important; /* unavailable (red) */
border-color: #ff8888;
border:none;
}
.weekly-calendar .strek-checkbox:disabled {
cursor: not-allowed;
opacity: 0.7;
}
.weekly-calendar .strek-checkbox:checked {
background: #00c48c !important;
border-color: #00c48c;
border:none;
}
/* Weekly calendar specific styles */
.weekly-calendar td {
text-align: center;
vertical-align: middle;
position: relative;
padding: 0px !important;
}
.weekly-calendar td:not(:first-child){
background: #ccffcc;
border-radius: 5px;
}
.weekly-calendar tr{
border:none !important;
}
td small {
font-size: 10px;
line-height: 1.2;
margin-top: 4px;
display: none;
}
.woocommerce div.product form.cart table{
border-collapse: separate;
border-spacing: 12px;
}
<script>
let displayWeekly='<?php echo $displayWeekly; ?>';
let displayPerday='<?php echo $displayPerday; ?>';
const strekAvail = <?php echo json_encode($strek_avail); ?>;
const strekMonthIdx = {};
Object.keys(strekAvail).forEach(stekId => {
strekMonthIdx[stekId] = 0; // Start at first month for each strek
});
const allowedDays = <?php echo json_encode($product->get_allowed_start_days()); ?>; // 1=Monday, 5=Friday, etc.
console.log('Streak Availability Data:', strekAvail);
console.log('Allowed Days:', allowedDays);
document.addEventListener('DOMContentLoaded', function() {
const weekList = <?php echo json_encode($week_list); ?>;
const weeksToShow = <?php echo $weeks_to_show; ?>;
const loadedMonths = <?php echo json_encode($loaded_months); ?>;
let weekOffset = 0;
let currentMonthIdx = 0;
const notAvailableDates = <?php echo json_encode($not_available_dates); ?>;
// Helper function to check if any day in a week is unavailable
const weekHasUnavailableDates = (weekDates, notAvailableDates) => {
for (let i = 0; i < weekDates.length; i++) {
if (notAvailableDates.includes(weekDates[i])) {
return true;
}
}
return false;
};
// WEEKLY CALENDAR
const renderCalendar = () => {
let html = `<table><thead><tr><th>Stek</th>`;
// Table headers (weeks)
for (let i = 0; i < weeksToShow; i++) {
const weekIdx = weekOffset + i;
if (weekIdx >= weekList.length) break;
const w = weekList[weekIdx];
// Find allowed weekday (e.g. Friday) in this week
let allowedDate = null;
for (let d = 0; d < w.dates.length; d++) {
const dateObj = new Date(w.dates[d]);
// getDay: 0=Sunday, 1=Monday, ..., 5=Friday, 6=Saturday
// allowedDays uses 1=Monday, 7=Sunday (ISO)
// Convert to ISO format: Saturday=6 becomes 6, Sunday=0 becomes 7
const isoDay = dateObj.getDay() === 0 ? 7 : dateObj.getDay();
if (allowedDays.includes(isoDay)) {
allowedDate = w.dates[d];
break;
}
}
let thLabel = `Week ${w.week}`;
if (allowedDate) {
const dateObj = new Date(allowedDate);
const startObj = new Date(w.dates[0]);
const endObj = new Date(w.dates[w.dates.length - 1]);
const startStr = startObj.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' });
const endStr = endObj.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' });
thLabel += `<br><small>${startStr} to ${endStr}</small>`;
}
html += `<th>${thLabel}</th>`;
}
html += `</tr></thead><tbody>`;
// For each stek, create a row
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
html += `<tr><td>${stekData.label}</td>`;
for (let i = 0; i < weeksToShow; i++) {
const weekIdx = weekOffset + i;
if (weekIdx >= weekList.length) break;
const w = weekList[weekIdx];
// Find allowed weekday in this week
let allowedDate = null;
for (let d = 0; d < w.dates.length; d++) {
const dateObj = new Date(w.dates[d]);
// Convert to ISO format: Saturday=6 becomes 6, Sunday=0 becomes 7
const isoDay = dateObj.getDay() === 0 ? 7 : dateObj.getDay();
if (allowedDays.includes(isoDay)) {
allowedDate = w.dates[d];
break;
}
}
// Check if this specific date is available for this specific stek
const isAvailable = allowedDate && stekData.available_dates.includes(allowedDate);
const unavailableClass = isAvailable ? '' : 'unavailable';
// Debug logging for this specific check
if (allowedDate) {
console.log(`Week ${w.week}: ${stekData.label} - Date: ${allowedDate}, Available: ${isAvailable}`);
// Show specific example for August 2nd
if (allowedDate === '2024-08-02') {
console.log(`🎯 August 2nd found! Week: ${w.week}, Resource: ${stekData.label}, Available: ${isAvailable}`);
}
}
html += `<td style="padding: 20px 10px;">
<input type="checkbox" class="week-checkbox strek-checkbox ${unavailableClass}"
id="week-check-${stekId}-${weekIdx}"
data-stek="${stekId}"
data-week="${w.week}"
data-year="${w.year}"
data-date="${allowedDate ? allowedDate : ''}"
${isAvailable ? '' : 'disabled'}>
${allowedDate
? `<br><small>${new Date(allowedDate).toLocaleDateString('nl-NL', { weekday: 'short', day: '2-digit', month: '2-digit' })}</small>`
: '<br><small>n.v.t.</small>'}
${isAvailable ? '<br><small style="color: green;">✓ Available</small>' : '<br><small style="color: red;">✗ Unavailable</small>'}
</td>`;
}
html += `</tr>`;
});
html += `</tbody></table>`;
document.getElementById('calendar-table-wrapper').innerHTML = html;
// Update navigation label
const navLabel = document.getElementById('week-navigation-label');
if (navLabel && weekList.length > 0) {
const firstWeek = weekList[weekOffset];
const lastWeek = weekList[Math.min(weekOffset + weeksToShow - 1, weekList.length - 1)];
if (firstWeek && lastWeek) {
const firstDate = new Date(firstWeek.start);
const lastDate = new Date(lastWeek.start);
lastDate.setDate(lastDate.getDate() + 6);
navLabel.textContent = `Weeks ${firstWeek.week} - ${lastWeek.week} (${firstDate.toLocaleDateString('nl-NL')} - ${lastDate.toLocaleDateString('nl-NL')})`;
}
}
// Selection highlight for checkboxes and event handlers
document.querySelectorAll('.weekly-calendar .week-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', () => {
const td = checkbox.closest('td');
if (checkbox.checked) {
// Uncheck all other week checkboxes across all streks
document.querySelectorAll('.weekly-calendar .week-checkbox').forEach(cb => {
if (cb !== checkbox) {
cb.checked = false;
cb.closest('td').classList.remove('selected');
}
});
td.classList.add('selected');
// Update the booking form fields
const strekId = checkbox.getAttribute('data-stek');
const productId = "<?php echo $product->get_id(); ?>";
const selectedDate = checkbox.getAttribute('data-date');
// Set the check-in (start date) field
const startDateInput = document.getElementById(`yith-wcbk-booking-start-date-${productId}`);
if (startDateInput) {
startDateInput.value = selectedDate;
startDateInput.dispatchEvent(new Event('input', { bubbles: true }));
startDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(startDateInput).datepicker) {
jQuery(startDateInput).datepicker('setDate', selectedDate);
}
}
// Set the check-out (end date) field to 7 days after check-in
const endDateInput = document.getElementById(`yith-wcbk-booking-end-date-${productId}`);
if (endDateInput) {
const dateObj = new Date(selectedDate);
dateObj.setDate(dateObj.getDate() + 7); // 7 days (week)
const yyyy = dateObj.getFullYear();
const mm = String(dateObj.getMonth() + 1).padStart(2, '0');
const dd = String(dateObj.getDate()).padStart(2, '0');
const endDate = `${yyyy}-${mm}-${dd}`;
endDateInput.value = endDate;
endDateInput.dispatchEvent(new Event('input', { bubbles: true }));
endDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(endDateInput).datepicker) {
jQuery(endDateInput).datepicker('setDate', endDate);
}
}
// Update strek dropdown
const select = document.querySelector(`#yith-wcbk-booking-resources-${productId} select.yith-wcbk-selector__field`);
if (select) {
select.value = strekId;
const event = new Event('change', { bubbles: true });
select.dispatchEvent(event);
const customItem = document.querySelector(`#yith-wcbk-booking-resources-${productId} .yith-wcbk-selector__item[data-key="${strekId}"]`);
if (customItem) {
customItem.click();
}
}
} else {
td.classList.remove('selected');
}
});
});
};
document.getElementById('prev-week-btn')?.remove();
document.getElementById('next-week-btn')?.remove();
const navDiv = document.createElement('div');
navDiv.style.display = "flex";
navDiv.style.justifyContent = "center";
navDiv.style.marginBottom = "10px";
navDiv.innerHTML = `
<button type="button" id="prev-week-btn" style="display:<?php echo $displayWeekly; ?>;"> <img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/left-chevron-svgrepo-com.svg" alt="Previous" width="25" height="25"> </button>
<div style="flex: 1; text-align: center; font-weight: bold;display:<?php echo $displayWeekly; ?>;" id="week-navigation-label">Select a week</div>
<button type="button" id="next-week-btn" style="display:<?php echo $displayWeekly; ?>;"> <img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/right-chevron-svgrepo-com.svg" alt="Previous" width="25" height="25"> </button>
`;
document.getElementById('calendar-table-wrapper').before(navDiv);
document.getElementById('prev-week-btn').addEventListener('click', () => {
if (weekOffset - weeksToShow >= 0) {
weekOffset -= weeksToShow;
} else {
weekOffset = 0;
}
renderCalendar();
});
document.getElementById('next-week-btn').addEventListener('click', () => {
if (weekOffset + weeksToShow < weekList.length) {
weekOffset += weeksToShow;
renderCalendar();
}
});
// MONTHLY CALENDAR (one month at a time)
const renderMonthlyCalendar = () => {
if (loadedMonths.length === 0) {
document.getElementById('monthly-calendar-scroll').innerHTML = '<p>No months available.</p>';
document.getElementById('monthly-calendar-controls').style.display = 'none';
return;
}
document.getElementById('monthly-calendar-controls').style.display = 'none'; // Hide global controls
let html = '';
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
html += renderStrekMonthBlock(stekId, stekData);
});
document.getElementById('monthly-calendar-scroll').innerHTML = html;
// Add event listeners for each strek's navigation
Object.keys(strekAvail).forEach(stekId => {
document.getElementById(`prev-month-btn-${stekId}`).addEventListener('click', () => {
if (strekMonthIdx[stekId] > 0) {
strekMonthIdx[stekId]--;
renderMonthlyCalendar();
}
});
document.getElementById(`next-month-btn-${stekId}`).addEventListener('click', () => {
if (strekMonthIdx[stekId] < loadedMonths.length - 1) {
strekMonthIdx[stekId]++;
renderMonthlyCalendar();
}
});
});
// Add scrollable class if needed
const strekCount = Object.keys(strekAvail).length;
const scrollDiv = document.getElementById('monthly-calendar-scroll');
if (strekCount > 2) {
scrollDiv.classList.add('scrollable');
} else {
scrollDiv.classList.remove('scrollable');
}
};
const renderStrekMonthBlock = (stekId, stekData) => {
const idx = strekMonthIdx[stekId];
const [year, month] = loadedMonths[idx].split('-').map(Number);
const availableDates = Array.isArray(stekData.available_dates) ? stekData.available_dates : [];
// Get first and last day of month
const firstDay = new Date(year, month - 1, 1);
const lastDay = new Date(year, month, 0);
const daysInMonth = lastDay.getDate();
let html = `<div class="strek-calendar-block">
<div style="font-weight:bold; margin-bottom:20px;">${stekData.label}</div>
<div style="text-align:center; margin-bottom:10px; display:flex; align-items: center; justify-content: space-between;">
<button type="button" id="prev-month-btn-${stekId}" class="prev_month"><img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/left-chevron-svgrepo-com.svg" alt="Next" width="25" height="25"> </button>
<span style="font-weight:bold; margin:0 10px;">${firstDay.toLocaleString('default', { month: 'long', year: 'numeric' })}</span>
<button type="button" id="next-month-btn-${stekId}" class="next_month"><img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/right-chevron-svgrepo-com.svg" alt="Next" width="25" height="25"></button>
</div>
<table>
<thead>
<tr>
<th>Sa</th><th>Su</th><th>Mo</th><th>Tu</th><th>We</th><th>Th</th><th>Fr</th>
</tr>
</thead>
<tbody>
<tr>`;
// Calculate the day of week for the 1st (Saturday=0)
let dayOfWeek = (firstDay.getDay() + 1) % 7;
for (let i = 0; i < dayOfWeek; i++) html += '<td></td>';
for (let day = 1; day <= daysInMonth; day++) {
if ((dayOfWeek % 7 === 0) && day !== 1) html += '</tr><tr>';
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
const isAvailable = availableDates.includes(dateStr);
const unavailableClass = isAvailable ? '' : 'unavailable';
const disabledAttr = isAvailable ? '' : 'disabled';
html += `<td>
<input type="checkbox" class="day-checkbox strek-checkbox ${unavailableClass}"
id="day-${stekId}-${year}-${month}-${day}"
data-date="${dateStr}"
data-stek="${stekId}"
${disabledAttr}>
<label for="day-${stekId}-${year}-${month}-${day}">${day}</label>
</td>`;
dayOfWeek++;
}
while (dayOfWeek % 7 !== 0) { html += '<td></td>'; dayOfWeek++; }
html += '</tr></tbody></table></div>';
return html;
};
const renderSingleMonth = (year, month) => {
// For each strek, render a classic calendar
let html = '';
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
// Get first and last day of month
const firstDay = new Date(year, month - 1, 1);
const lastDay = new Date(year, month, 0);
const daysInMonth = lastDay.getDate();
html += `<div class="strek-calendar-block">
<div style="font-weight:bold; margin-bottom:4px;">${stekData.label}</div>
<table>
<caption>${firstDay.toLocaleString('default', { month: 'long' })} ${year}</caption>
<thead>
<tr>
<th>Sa</th><th>Su</th><th>Mo</th><th>Tu</th><th>We</th><th>Th</th><th>Fr</th>
</tr>
</thead>
<tbody>
<tr>`;
// Calculate the day of week for the 1st (Saturday=0)
let dayOfWeek = (firstDay.getDay() + 1) % 7;
for (let i = 0; i < dayOfWeek; i++) html += '<td></td>';
for (let day = 1; day <= daysInMonth; day++) {
if ((dayOfWeek % 7 === 0) && day !== 1) html += '</tr><tr>';
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
html += `<td>
<input type="checkbox" class="day-checkbox strek-checkbox" id="day-${stekId}-${year}-${month}-${day}" data-date="${dateStr}" data-stek="${stekId}">
<label for="day-${stekId}-${year}-${month}-${day}">${day}</label>
</td>`;
dayOfWeek++;
}
while (dayOfWeek % 7 !== 0) { html += '<td></td>'; dayOfWeek++; }
html += '</tr></tbody></table></div>';
});
return html;
};
// TOGGLE LOGIC
document.getElementById('toggle-week').addEventListener('click', function() {
document.getElementById('toggle-week').classList.add('active');
document.getElementById('toggle-day').classList.remove('active');
document.getElementById('calendar-table-wrapper').style.display = '';
document.getElementById('monthly-calendar-wrapper').style.display = 'none';
document.getElementById('monthly-calendar-controls').style.display = 'none';
navDiv.style.display = '';
renderCalendar();
});
document.getElementById('toggle-day').addEventListener('click', function() {
document.getElementById('toggle-day').classList.add('active');
document.getElementById('toggle-week').classList.remove('active');
document.getElementById('calendar-table-wrapper').style.display = 'none';
document.getElementById('monthly-calendar-wrapper').style.display = '';
document.getElementById('monthly-calendar-controls').style.display = '';
navDiv.style.display = 'none';
renderMonthlyCalendar();
});
// Initial render
if(displayWeekly=='block'){
renderCalendar();
}else{
renderMonthlyCalendar();
}
document.getElementById('prev-month-btn').addEventListener('click', () => {
if (currentMonthIdx > 0) {
currentMonthIdx--;
renderMonthlyCalendar();
}
});
document.getElementById('next-month-btn').addEventListener('click', () => {
if (currentMonthIdx < loadedMonths.length - 1) {
currentMonthIdx++;
renderMonthlyCalendar();
}
});
document.addEventListener('change', function(e) {
if (
e.target.classList.contains('day-checkbox') &&
e.target.checked &&
!e.target.disabled
) {
// Uncheck all other day-checkboxes
document.querySelectorAll('.day-checkbox').forEach(cb => {
if (cb !== e.target) cb.checked = false;
});
const strekId = e.target.getAttribute('data-stek');
const productId = "<?php echo $product->get_id(); ?>";
const selectedDate = e.target.getAttribute('data-date');
// Set the check-in (start date) field and fire change event
const startDateInput = document.getElementById(`yith-wcbk-booking-start-date-${productId}`);
startDateInput.value = selectedDate;
// Fire both input and change events
startDateInput.dispatchEvent(new Event('input', { bubbles: true }));
startDateInput.dispatchEvent(new Event('change', { bubbles: true }));
// If a jQuery datepicker is attached, update it as well
if (typeof jQuery !== 'undefined' && jQuery(startDateInput).datepicker) {
jQuery(startDateInput).datepicker('setDate', selectedDate);
}
// Set the check-out (end date) field to one day after check-in
const endDateInput = document.getElementById(`yith-wcbk-booking-end-date-${productId}`);
if (endDateInput) {
// Calculate next day
const dateObj = new Date(selectedDate);
dateObj.setDate(dateObj.getDate() + 1);
// Format as yyyy-mm-dd
const yyyy = dateObj.getFullYear();
const mm = String(dateObj.getMonth() + 1).padStart(2, '0');
const dd = String(dateObj.getDate()).padStart(2, '0');
const nextDay = `${yyyy}-${mm}-${dd}`;
endDateInput.value = nextDay;
endDateInput.dispatchEvent(new Event('input', { bubbles: true }));
endDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(endDateInput).datepicker) {
jQuery(endDateInput).datepicker('setDate', nextDay);
}
}
// Strek dropdown logic (as before)
const select = document.querySelector(`#yith-wcbk-booking-resources-${productId} select.yith-wcbk-selector__field`);
if (select) {
select.value = strekId;
const event = new Event('change', { bubbles: true });
select.dispatchEvent(event);
const customItem = document.querySelector(`#yith-wcbk-booking-resources-${productId} .yith-wcbk-selector__item[data-key="${strekId}"]`);
if (customItem) {
customItem.click();
}
}
}
});
});
</script>
Now I want to add this calender also in wp-admin product detail page so adjust code like this
### ✅ Use this for week header label showing date range (e.g. `25-10 to 28-10`):
const startObj = new Date(w.dates[0]);
const endObj = new Date(w.dates[w.dates.length - 1]);
const startStr = startObj.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' });
const endStr = endObj.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' });
thLabel += `<br><small>${startStr} to ${endStr}</small>`;
This ensures each week's column shows the **correct visible range** in Dutch format (`dd-mm`) and avoids confusion over which days are being represented per week column.
Let me know if you now want to match this with actual **resource availability** per week in the table.
—
### ✅ **Step-by-Step: Add Weekly Resource Availability Calendar to Product Admin Page**
—
### 1. **Hook into Product Data Panel**
In your custom plugin or theme’s `functions.php`, add:
add_action('woocommerce_product_data_panels', 'custom_booking_resource_availability_admin_panel');
function custom_booking_resource_availability_admin_panel() {
global $post;
$product = wc_get_product($post->ID);
if (!$product || $product->get_type() !== 'booking') {
return;
}
?>
<div id="booking_resource_availability_data" class="panel woocommerce_options_panel">
<h2>Resource Availability Calendar</h2>
<div id="resource-availability-calendar" data-product-id="<?php echo esc_attr($product->get_id()); ?>">
Loading calendar...
</div>
</div>
<?php
}
—
### 2. **Enqueue JS and CSS in Admin**
add_action('admin_enqueue_scripts', 'enqueue_admin_resource_calendar_script');
function enqueue_admin_resource_calendar_script($hook) {
if ($hook !== 'post.php' && $hook !== 'post-new.php') return;
global $post;
if (!$post || get_post_type($post) !== 'product') return;
wp_enqueue_script('admin-resource-calendar', plugin_dir_url(__FILE__) . 'js/admin-resource-calendar.js', ['jquery'], null, true);
wp_localize_script('admin-resource-calendar', 'ResourceCalendarData', [
'ajax_url' => admin_url('admin-ajax.php'),
'product_id' => $post->ID
]);
}
—
### 3. **Create `admin-resource-calendar.js`**
In `js/admin-resource-calendar.js` (create the file in your plugin or theme):
jQuery(document).ready(function ($) {
const $container = $('#resource-availability-calendar');
if (!$container.length) return;
const productId = $container.data('product-id');
$.post(ResourceCalendarData.ajax_url, {
action: 'get_resource_availability',
product_id: productId
}, function (response) {
if (response.success) {
renderCalendar(response.data);
} else {
$container.html('<p>Failed to load calendar.</p>');
}
});
function renderCalendar(data) {
const { weeks, resources } = data;
let html = `<table class="wp-list-table widefat striped"><thead><tr><th>Stek</th>`;
weeks.forEach(w => {
const start = new Date(w.dates[0]);
const end = new Date(w.dates[w.dates.length - 1]);
const label = `${start.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' })} to ${end.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' })}`;
html += `<th>Week ${w.week}<br><small>${label}</small></th>`;
});
html += `</tr></thead><tbody>`;
Object.entries(resources).forEach(([id, r]) => {
html += `<tr><td><strong>${r.label}</strong></td>`;
weeks.forEach(w => {
const match = w.dates.some(d => r.available_dates.includes(d));
html += `<td style="text-align:center;">${match ? '✅' : '❌'}</td>`;
});
html += `</tr>`;
});
html += `</tbody></table>`;
$container.html(html);
}
});
—
### 4. **Add AJAX Endpoint**
In your plugin or theme:
add_action('wp_ajax_get_resource_availability', 'get_admin_product_resource_availability');
function get_admin_product_resource_availability() {
$product_id = absint($_POST['product_id'] ?? 0);
if (!$product_id) wp_send_json_error();
$product = wc_get_product($product_id);
if (!$product || !method_exists($product, 'get_resources_data')) {
wp_send_json_error();
}
$resources_data = $product->get_resources_data();
$availability_handler_class = 'YITH_WCBK_Resource_Availability_Handler';
$weeks = [];
$resources = [];
$days_to_check = 90;
$start_date = strtotime('today');
$end_date = strtotime("+$days_to_check days");
for ($date = $start_date; $date < $end_date; $date += 7 * DAY_IN_SECONDS) {
$week = [];
for ($i = 0; $i < 7; $i++) {
$day = date('Y-m-d', $date + $i * DAY_IN_SECONDS);
$week[] = $day;
}
$weeks[] = [
'week' => date('W', strtotime($week[0])),
'dates' => $week
];
}
foreach ($resources_data as $r_data) {
$resource = $r_data->get_resource();
$handler = new $availability_handler_class();
$handler->init($resource, $product);
$available_dates = [];
for ($date = $start_date; $date < $end_date; $date += DAY_IN_SECONDS) {
$is_available = $handler->check_booked_availability($date, $date + DAY_IN_SECONDS);
if ($is_available) {
$available_dates[] = date('Y-m-d', $date);
}
}
$resources[$resource->get_id()] = [
'label' => $resource->get_name(),
'available_dates' => $available_dates
];
}
wp_send_json_success([
'weeks' => $weeks,
'resources' => $resources
]);
}
—
### 🔧 Optional Styling
You can style the calendar inside the admin with:
#resource-availability-calendar table td {
text-align: center;
}
—
Would you like me to generate this as a ready-to-install **plugin folder** or all in one ZIP?
—
### ✅ Goal:
Add a new **Calendar** tab to the **product data panel**, and load your availability calendar inside it.
—
### 🧩 STEP 1: Add "Calendar" Tab
add_filter('woocommerce_product_data_tabs', function($tabs) {
$tabs['calendar_tab'] = array(
'label' => __('Calendar', 'your-textdomain'),
'target' => 'calendar_tab_data',
'class' => array(),
'priority' => 99,
);
return $tabs;
});
—
### 🧩 STEP 2: Add Calendar Tab Panel (HTML container)
add_action('woocommerce_product_data_panels', function() {
global $post;
$product = wc_get_product($post->ID);
if (!$product || $product->get_type() !== 'booking') return;
?>
<div id="calendar_tab_data" class="panel woocommerce_options_panel">
<h2><?php _e('Resource Availability Calendar', 'your-textdomain'); ?></h2>
<div id="admin-calendar-container" data-product-id="<?php echo esc_attr($product->get_id()); ?>">
Loading calendar...
</div>
</div>
<?php
});
—
### 🧩 STEP 3: Enqueue JS + Localize
add_action('admin_enqueue_scripts', function($hook) {
if (!in_array($hook, ['post.php', 'post-new.php'])) return;
global $post;
if (get_post_type($post) !== 'product') return;
wp_enqueue_script('admin-calendar-tab', plugin_dir_url(__FILE__) . 'js/admin-calendar-tab.js', ['jquery'], null, true);
wp_localize_script('admin-calendar-tab', 'CalendarTabData', [
'ajax_url' => admin_url('admin-ajax.php'),
'product_id' => $post->ID
]);
});
—
### 🧩 STEP 4: Add `admin-calendar-tab.js`
Create a file at:
`/js/admin-calendar-tab.js`
jQuery(function($) {
const $container = $('#admin-calendar-container');
const productId = $container.data('product-id');
$.post(CalendarTabData.ajax_url, {
action: 'get_calendar_tab_data',
product_id: productId
}, function(response) {
if (response.success) {
renderCalendar(response.data);
} else {
$container.html('<p>Failed to load calendar.</p>');
}
});
function renderCalendar(data) {
const { weeks, resources } = data;
let html = `<table class="wp-list-table widefat striped"><thead><tr><th>Stek</th>`;
weeks.forEach(w => {
const start = new Date(w.dates[0]);
const end = new Date(w.dates[w.dates.length - 1]);
const label = `${start.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' })} to ${end.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' })}`;
html += `<th>Week ${w.week}<br><small>${label}</small></th>`;
});
html += `</tr></thead><tbody>`;
Object.entries(resources).forEach(([id, r]) => {
html += `<tr><td><strong>${r.label}</strong></td>`;
weeks.forEach(w => {
const match = w.dates.some(d => r.available_dates.includes(d));
html += `<td>${match ? '✅' : '❌'}</td>`;
});
html += `</tr>`;
});
html += `</tbody></table>`;
$container.html(html);
}
});
—
### 🧩 STEP 5: Handle AJAX (`functions.php` or plugin)
add_action('wp_ajax_get_calendar_tab_data', function() {
$product_id = absint($_POST['product_id'] ?? 0);
$product = wc_get_product($product_id);
if (!$product || !method_exists($product, 'get_resources_data')) {
wp_send_json_error();
}
$resources_data = $product->get_resources_data();
$handler_class = 'YITH_WCBK_Resource_Availability_Handler';
$weeks = [];
$resources = [];
$start = strtotime('today');
$end = strtotime('+60 days');
for ($date = $start; $date < $end; $date += 7 * DAY_IN_SECONDS) {
$week = [];
for ($i = 0; $i < 7; $i++) {
$day = date('Y-m-d', $date + $i * DAY_IN_SECONDS);
$week[] = $day;
}
$weeks[] = [
'week' => date('W', strtotime($week[0])),
'dates' => $week
];
}
foreach ($resources_data as $rd) {
$resource = $rd->get_resource();
if (!$resource) continue;
$handler = new $handler_class();
$handler->init($resource, $product);
$available = [];
for ($d = $start; $d < $end; $d += DAY_IN_SECONDS) {
if ($handler->check_booked_availability($d, $d + DAY_IN_SECONDS)) {
$available[] = date('Y-m-d', $d);
}
}
$resources[$resource->get_id()] = [
'label' => $resource->get_name(),
'available_dates' => $available
];
}
wp_send_json_success([
'weeks' => $weeks,
'resources' => $resources
]);
});
—
### ✅ Result:
– New **"Calendar"** tab appears on Booking products.
– Shows **availability by week** per resource (stek) with ✅/❌.
– Loads via AJAX for performance.
—
Let me know if you want:
– A toggle for **day vs week** view.
– To show **quantity available** instead of just ✅/❌.
– A ZIP/plugin version for easy install.
class Booking_Addon {
public function __construct() {
add_filter('woocommerce_product_data_tabs', array($this, 'add_calendar_tab'));
}
public function add_calendar_tab($tabs) {
$tabs['calendar_tab'] = array(
'label' => __('Calendar', 'your-textdomain'),
'target' => 'calendar_tab_data',
'class' => array(),
'priority' => 99,
);
return $tabs;
}
}
$booking_addon = new Booking_Addon();
add_action('plugins_loaded', array($booking_addon, 'init'));
Fatal error: Uncaught TypeError: call_user_func_array(): Argument #1 ($callback) must be a valid callback, class Booking_Addon does not have a method "init" in /var/www/html/wp-includes/class-wp-hook.php:324 Stack trace: #0 /var/www/html/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters() #1 /var/www/html/wp-includes/plugin.php(517): WP_Hook->do_action() #2 /var/www/html/wp-settings.php(578): do_action() #3 /var/www/html/wp-config.php(105): require_once('…') #4 /var/www/html/wp-load.php(50): require_once('…') #5 /var/www/html/wp-admin/admin.php(35): require_once('…') #6 /var/www/html/wp-admin/plugins.php(10): require_once('…') #7 {main} thrown in /var/www/html/wp-includes/class-wp-hook.php on line 324
Fatal error: call_user_func_array(): Argument #1 ($callback) must be a valid callback, class Booking_Addon does not have a method "init"
means that you tried to hook `Booking_Addon::init` using:
add_action('plugins_loaded', array($booking_addon, 'init'));
…but your class **does not define an `init()` method**, so the callback is invalid.
—
### ✅ Solution
You have **two options** depending on what you want:
—
### 🔹 **Option 1: Remove the `plugins_loaded` hook** (recommended if you’re already calling everything in `__construct`):
if (!defined('ABSPATH')) exit;
class Booking_Addon {
public function __construct() {
add_filter('woocommerce_product_data_tabs', array($this, 'add_calendar_tab'));
}
public function add_calendar_tab($tabs) {
$tabs['calendar_tab'] = array(
'label' => __('Calendar', 'your-textdomain'),
'target' => 'calendar_tab_data',
'class' => array(),
'priority' => 99,
);
return $tabs;
}
}
// Just instantiate it directly
new Booking_Addon();
✅ This works fine because `add_filter()` is safe to run during plugin load.
—
### 🔹 **Option 2: If you want to defer logic to `init()` method** (then define it!)
if (!defined('ABSPATH')) exit;
class Booking_Addon {
public function __construct() {
// constructor left empty
}
public function init() {
add_filter('woocommerce_product_data_tabs', array($this, 'add_calendar_tab'));
}
public function add_calendar_tab($tabs) {
$tabs['calendar_tab'] = array(
'label' => __('Calendar', 'your-textdomain'),
'target' => 'calendar_tab_data',
'class' => array(),
'priority' => 99,
);
return $tabs;
}
}
// Initialize plugin properly
$booking_addon = new Booking_Addon();
add_action('plugins_loaded', array($booking_addon, 'init'));
✅ This works if you want to wait until `plugins_loaded` before hooking.
—
### ✅ Recommendation
If you don’t have any dependencies on other plugins or WordPress components during early load — just go with **Option 1** (simpler and cleaner).
Let me know if you'd also like to register the calendar JS/CSS inside the same class.
Here’s the full continuation for your `Booking_Addon` class to show the **calendar content** inside the tab panel:
—
### ✅ Updated Full Class
if (!defined('ABSPATH')) exit;
class Booking_Addon {
public function __construct() {
add_filter('woocommerce_product_data_tabs', array($this, 'add_calendar_tab'));
add_action('woocommerce_product_data_panels', array($this, 'add_calendar_tab_content'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_calendar_script'));
add_action('wp_ajax_get_booking_calendar_data', array($this, 'ajax_get_booking_calendar_data'));
}
public function add_calendar_tab($tabs) {
$tabs['calendar_tab'] = array(
'label' => __('Calendar', 'your-textdomain'),
'target' => 'calendar_tab_data',
'class' => array(),
'priority' => 99,
);
return $tabs;
}
public function add_calendar_tab_content() {
global $post;
?>
<div id="calendar_tab_data" class="panel woocommerce_options_panel">
<h3><?php _e('Booking Calendar Availability', 'your-textdomain'); ?></h3>
<div id="admin-calendar-container" data-product-id="<?php echo esc_attr($post->ID); ?>">
Loading calendar...
</div>
</div>
<?php
}
public function enqueue_admin_calendar_script($hook) {
if (!in_array($hook, ['post.php', 'post-new.php'])) return;
global $post;
if (get_post_type($post) !== 'product') return;
wp_enqueue_script('admin-calendar-tab', plugin_dir_url(__FILE__) . 'js/admin-calendar-tab.js', ['jquery'], null, true);
wp_localize_script('admin-calendar-tab', 'CalendarTabData', [
'ajax_url' => admin_url('admin-ajax.php'),
'product_id' => $post->ID
]);
}
public function ajax_get_booking_calendar_data() {
$product_id = absint($_POST['product_id'] ?? 0);
$product = wc_get_product($product_id);
if (!$product || !method_exists($product, 'get_resources_data')) {
wp_send_json_error();
}
$resources_data = $product->get_resources_data();
$weeks = [];
$resources = [];
$start = strtotime('today');
$end = strtotime('+60 days');
for ($date = $start; $date < $end; $date += 7 * DAY_IN_SECONDS) {
$week = [];
for ($i = 0; $i < 7; $i++) {
$day = date('Y-m-d', $date + $i * DAY_IN_SECONDS);
$week[] = $day;
}
$weeks[] = [
'week' => date('W', strtotime($week[0])),
'dates' => $week
];
}
foreach ($resources_data as $rd) {
$resource = $rd->get_resource();
if (!$resource) continue;
$handler = new YITH_WCBK_Resource_Availability_Handler();
$handler->init($resource, $product);
$available = [];
for ($d = $start; $d < $end; $d += DAY_IN_SECONDS) {
if ($handler->check_booked_availability($d, $d + DAY_IN_SECONDS)) {
$available[] = date('Y-m-d', $d);
}
}
$resources[$resource->get_id()] = [
'label' => $resource->get_name(),
'available_dates' => $available
];
}
wp_send_json_success([
'weeks' => $weeks,
'resources' => $resources
]);
}
}
new Booking_Addon();
—
### ✅ Create the JavaScript File (`js/admin-calendar-tab.js`)
jQuery(function($) {
const $container = $('#admin-calendar-container');
const productId = $container.data('product-id');
$.post(CalendarTabData.ajax_url, {
action: 'get_booking_calendar_data',
product_id: productId
}, function(response) {
if (response.success) {
renderCalendar(response.data);
} else {
$container.html('<p>Failed to load calendar.</p>');
}
});
function renderCalendar(data) {
const { weeks, resources } = data;
let html = `<table class="wp-list-table widefat striped"><thead><tr><th>Strek</th>`;
weeks.forEach(w => {
const start = new Date(w.dates[0]);
const end = new Date(w.dates[w.dates.length - 1]);
const label = `${start.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' })} to ${end.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' })}`;
html += `<th>Week ${w.week}<br><small>${label}</small></th>`;
});
html += `</tr></thead><tbody>`;
Object.entries(resources).forEach(([id, r]) => {
html += `<tr><td><strong>${r.label}</strong></td>`;
weeks.forEach(w => {
const match = w.dates.some(d => r.available_dates.includes(d));
html += `<td>${match ? '✅' : '❌'}</td>`;
});
html += `</tr>`;
});
html += `</tbody></table>`;
$container.html(html);
}
});
—
### ✅ Folder Structure Example
your-plugin/
├── your-plugin.php
└── js/
└── admin-calendar-tab.js
—
You now have a working **Calendar tab** inside the WooCommerce product admin that dynamically shows resource availability by week ✅❌.
Let me know if you want:
– Color-coded availability cells (green/red),
– Toggle for monthly view,
– Editable bookings inside admin.
$slug = $product->get_slug();
### ✅ To get the product slug (i.e., post slug / URL-friendly name):
You can use the built-in WordPress `get_post_field()` function:
$slug = get_post_field( 'post_name', $product->get_id() );
### ✅ Alternatively, if you have the product ID:
$product_id = 123; // or $product->get_id()
$slug = get_post_field( 'post_name', $product_id );
—
### 🔹 Example inside WooCommerce context:
$product = wc_get_product( get_the_ID() );
$slug = get_post_field( 'post_name', $product->get_id() );
echo 'Slug: ' . esc_html( $slug );
Let me know if you need to fetch slugs for **variations**, **resources**, or **custom post types** — the approach is similar.
chane it to admin roduct url
$admin_product_url = get_edit_post_link( $weekly_product->ID );
So, change:
$weekly_product_url = get_permalink($weekly_product->ID);
to:
$weekly_product_url = get_edit_post_link($weekly_product->ID);
—
### ✅ Example with anchor link:
echo '<a href="' . esc_url( get_edit_post_link($weekly_product->ID) ) . '" target="_blank">Edit Product</a>';
This will take you to the **WooCommerce product edit screen** in the WordPress admin.
$current_year,
$current_month,
$next_year,
$next_month,
$min_date,
$min_date_timestamp,
$max_date,
$max_date_timestamp,
$default_start_date,
$default_end_date,
$months_to_load,
$ajax_load_months,
$loaded_months
) = yith_plugin_fw_extract(
$date_info,
'current_year', 'current_month', 'next_year', 'next_month',
'min_date', 'min_date_timestamp', 'max_date', 'max_date_timestamp',
'default_start_date', 'default_end_date', 'months_to_load', 'ajax_load_months', 'loaded_months'
);
it says $date_info not found
—
### 🔧 What you need to do:
You must **initialize `$date_info`** with the proper array before calling `yith_plugin_fw_extract()`.
—
### ✅ Example Fix:
If you're working inside a context where you want to load current and next month info (e.g., for calendar booking availability), use the helper:
$date_info = yith_wcbk_get_booking_form_date_info(); // This function returns the needed array
So your full code should be:
$date_info = yith_wcbk_get_booking_form_date_info();
list(
$current_year,
$current_month,
$next_year,
$next_month,
$min_date,
$min_date_timestamp,
$max_date,
$max_date_timestamp,
$default_start_date,
$default_end_date,
$months_to_load,
$ajax_load_months,
$loaded_months
) = yith_plugin_fw_extract(
$date_info,
'current_year', 'current_month', 'next_year', 'next_month',
'min_date', 'min_date_timestamp', 'max_date', 'max_date_timestamp',
'default_start_date', 'default_end_date', 'months_to_load', 'ajax_load_months', 'loaded_months'
);
—
### 🧠 Explanation:
– `yith_wcbk_get_booking_form_date_info()` generates all the booking date settings like min/max/default date ranges.
– `yith_plugin_fw_extract()` is a YITH utility that safely pulls multiple keys from an array into variables in one go.
—
Let me know if you want to **limit it to one month only**, or if you're in a **custom admin context**, in which case you'd construct `$date_info` manually.
> **`Too few arguments to function yith_wcbk_get_booking_form_date_info()`**
means the function `yith_wcbk_get_booking_form_date_info()` **requires at least one parameter**, which you're not passing.
—
### ✅ Correct Usage:
The function expects a **`WC_Product_Booking` object** as the first argument.
$product = wc_get_product( $product_id );
Then:
$date_info = yith_wcbk_get_booking_form_date_info( $product );
—
### ✅ Full Working Example:
$product_id = 123; // Replace with your actual product ID
$product = wc_get_product( $product_id );
if ( $product && $product instanceof WC_Product_Booking ) {
$date_info = yith_wcbk_get_booking_form_date_info( $product );
list(
$current_year,
$current_month,
$next_year,
$next_month,
$min_date,
$min_date_timestamp,
$max_date,
$max_date_timestamp,
$default_start_date,
$default_end_date,
$months_to_load,
$ajax_load_months,
$loaded_months
) = yith_plugin_fw_extract(
$date_info,
'current_year', 'current_month', 'next_year', 'next_month',
'min_date', 'min_date_timestamp', 'max_date', 'max_date_timestamp',
'default_start_date', 'default_end_date', 'months_to_load', 'ajax_load_months', 'loaded_months'
);
}
—
### 🧠 Pro Tip:
Always check the expected arguments of YITH functions. In this case:
function yith_wcbk_get_booking_form_date_info( WC_Product_Booking $product, ... )
Let me know if your product is not of type `WC_Product_Booking` and you need to convert or validate it.
)
<script>
let displayWeekly='<?php echo $displayWeekly; ?>';
let displayPerday='<?php echo $displayPerday; ?>';
const strekAvail = <?php echo json_encode($strek_avail); ?>;
const strekMonthIdx = {};
Object.keys(strekAvail).forEach(stekId => {
strekMonthIdx[stekId] = 0; // Start at first month for each strek
});
const allowedDays = <?php echo json_encode($product->get_allowed_start_days()); ?>; // 1=Monday, 5=Friday, etc.
console.log('Streak Availability Data:', strekAvail);
console.log('Allowed Days:', allowedDays);
document.addEventListener('DOMContentLoaded', function() {
const weekList = <?php echo json_encode($week_list); ?>;
const weeksToShow = <?php echo $weeks_to_show; ?>;
const loadedMonths = <?php echo json_encode($loaded_months); ?>;
let weekOffset = 0;
let currentMonthIdx = 0;
const notAvailableDates = <?php echo json_encode($not_available_dates); ?>;
// Helper function to check if any day in a week is unavailable
const weekHasUnavailableDates = (weekDates, notAvailableDates) => {
for (let i = 0; i < weekDates.length; i++) {
if (notAvailableDates.includes(weekDates[i])) {
return true;
}
}
return false;
};
// WEEKLY CALENDAR
const renderCalendar = () => {
let html = `<table><thead><tr><th>Stek</th>`;
// Table headers (weeks)
for (let i = 0; i < weeksToShow; i++) {
const weekIdx = weekOffset + i;
if (weekIdx >= weekList.length) break;
const w = weekList[weekIdx];
// Find allowed weekday (e.g. Friday) in this week
let allowedDate = null;
for (let d = 0; d < w.dates.length; d++) {
const dateObj = new Date(w.dates[d]);
// getDay: 0=Sunday, 1=Monday, ..., 5=Friday, 6=Saturday
// allowedDays uses 1=Monday, 7=Sunday (ISO)
// Convert to ISO format: Saturday=6 becomes 6, Sunday=0 becomes 7
const isoDay = dateObj.getDay() === 0 ? 7 : dateObj.getDay();
if (allowedDays.includes(isoDay)) {
allowedDate = w.dates[d];
break;
}
}
let thLabel = `Week ${w.week}`;
if (allowedDate) {
const dateObj = new Date(allowedDate);
const startObj = new Date(w.dates[0]);
const endObj = new Date(w.dates[w.dates.length - 1]);
const startStr = startObj.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' });
const endStr = endObj.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' });
thLabel += `<br><small>${startStr} to ${endStr}</small>`;
}
html += `<th>${thLabel}</th>`;
}
html += `</tr></thead><tbody>`;
// For each stek, create a row
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
html += `<tr><td>${stekData.label}</td>`;
for (let i = 0; i < weeksToShow; i++) {
const weekIdx = weekOffset + i;
if (weekIdx >= weekList.length) break;
const w = weekList[weekIdx];
// Find allowed weekday in this week
let allowedDate = null;
for (let d = 0; d < w.dates.length; d++) {
const dateObj = new Date(w.dates[d]);
// Convert to ISO format: Saturday=6 becomes 6, Sunday=0 becomes 7
const isoDay = dateObj.getDay() === 0 ? 7 : dateObj.getDay();
if (allowedDays.includes(isoDay)) {
allowedDate = w.dates[d];
break;
}
}
// Check if this specific date is available for this specific stek
const isAvailable = allowedDate && stekData.available_dates.includes(allowedDate);
const unavailableClass = isAvailable ? '' : 'unavailable';
// Debug logging for this specific check
if (allowedDate) {
console.log(`Week ${w.week}: ${stekData.label} - Date: ${allowedDate}, Available: ${isAvailable}`);
// Show specific example for August 2nd
if (allowedDate === '2024-08-02') {
console.log(`🎯 August 2nd found! Week: ${w.week}, Resource: ${stekData.label}, Available: ${isAvailable}`);
}
}
html += `<td style="padding: 20px 10px;">
<input type="checkbox" class="week-checkbox strek-checkbox ${unavailableClass}"
id="week-check-${stekId}-${weekIdx}"
data-stek="${stekId}"
data-week="${w.week}"
data-year="${w.year}"
data-date="${allowedDate ? allowedDate : ''}"
${isAvailable ? '' : 'disabled'}>
${allowedDate
? `<br><small>${new Date(allowedDate).toLocaleDateString('nl-NL', { weekday: 'short', day: '2-digit', month: '2-digit' })}</small>`
: '<br><small>n.v.t.</small>'}
${isAvailable ? '<br><small style="color: green;">✓ Available</small>' : '<br><small style="color: red;">✗ Unavailable</small>'}
</td>`;
}
html += `</tr>`;
});
html += `</tbody></table>`;
document.getElementById('calendar-table-wrapper').innerHTML = html;
// Update navigation label
const navLabel = document.getElementById('week-navigation-label');
if (navLabel && weekList.length > 0) {
const firstWeek = weekList[weekOffset];
const lastWeek = weekList[Math.min(weekOffset + weeksToShow - 1, weekList.length - 1)];
if (firstWeek && lastWeek) {
const firstDate = new Date(firstWeek.start);
const lastDate = new Date(lastWeek.start);
lastDate.setDate(lastDate.getDate() + 6);
navLabel.textContent = `Weeks ${firstWeek.week} - ${lastWeek.week} (${firstDate.toLocaleDateString('nl-NL')} - ${lastDate.toLocaleDateString('nl-NL')})`;
}
}
// Selection highlight for checkboxes and event handlers
document.querySelectorAll('.weekly-calendar .week-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', () => {
const td = checkbox.closest('td');
if (checkbox.checked) {
// Uncheck all other week checkboxes across all streks
document.querySelectorAll('.weekly-calendar .week-checkbox').forEach(cb => {
if (cb !== checkbox) {
cb.checked = false;
cb.closest('td').classList.remove('selected');
}
});
td.classList.add('selected');
// Update the booking form fields
const strekId = checkbox.getAttribute('data-stek');
const productId = "<?php echo $product->get_id(); ?>";
const selectedDate = checkbox.getAttribute('data-date');
// Set the check-in (start date) field
const startDateInput = document.getElementById(`yith-wcbk-booking-start-date-${productId}`);
if (startDateInput) {
startDateInput.value = selectedDate;
startDateInput.dispatchEvent(new Event('input', { bubbles: true }));
startDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(startDateInput).datepicker) {
jQuery(startDateInput).datepicker('setDate', selectedDate);
}
}
// Set the check-out (end date) field to 7 days after check-in
const endDateInput = document.getElementById(`yith-wcbk-booking-end-date-${productId}`);
if (endDateInput) {
const dateObj = new Date(selectedDate);
dateObj.setDate(dateObj.getDate() + 7); // 7 days (week)
const yyyy = dateObj.getFullYear();
const mm = String(dateObj.getMonth() + 1).padStart(2, '0');
const dd = String(dateObj.getDate()).padStart(2, '0');
const endDate = `${yyyy}-${mm}-${dd}`;
endDateInput.value = endDate;
endDateInput.dispatchEvent(new Event('input', { bubbles: true }));
endDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(endDateInput).datepicker) {
jQuery(endDateInput).datepicker('setDate', endDate);
}
}
// Update strek dropdown
const select = document.querySelector(`#yith-wcbk-booking-resources-${productId} select.yith-wcbk-selector__field`);
if (select) {
select.value = strekId;
const event = new Event('change', { bubbles: true });
select.dispatchEvent(event);
const customItem = document.querySelector(`#yith-wcbk-booking-resources-${productId} .yith-wcbk-selector__item[data-key="${strekId}"]`);
if (customItem) {
customItem.click();
}
}
} else {
td.classList.remove('selected');
}
});
});
};
document.getElementById('prev-week-btn')?.remove();
document.getElementById('next-week-btn')?.remove();
const navDiv = document.createElement('div');
navDiv.style.display = "flex";
navDiv.style.justifyContent = "center";
navDiv.style.marginBottom = "10px";
navDiv.innerHTML = `
<button type="button" id="prev-week-btn" style="display:<?php echo $displayWeekly; ?>;"> <img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/left-chevron-svgrepo-com.svg" alt="Previous" width="25" height="25"> </button>
<div style="flex: 1; text-align: center; font-weight: bold;display:<?php echo $displayWeekly; ?>;" id="week-navigation-label">Select a week</div>
<button type="button" id="next-week-btn" style="display:<?php echo $displayWeekly; ?>;"> <img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/right-chevron-svgrepo-com.svg" alt="Previous" width="25" height="25"> </button>
`;
document.getElementById('calendar-table-wrapper').before(navDiv);
document.getElementById('prev-week-btn').addEventListener('click', () => {
if (weekOffset - weeksToShow >= 0) {
weekOffset -= weeksToShow;
} else {
weekOffset = 0;
}
renderCalendar();
});
document.getElementById('next-week-btn').addEventListener('click', () => {
if (weekOffset + weeksToShow < weekList.length) {
weekOffset += weeksToShow;
renderCalendar();
}
});
// MONTHLY CALENDAR (one month at a time)
const renderMonthlyCalendar = () => {
if (loadedMonths.length === 0) {
document.getElementById('monthly-calendar-scroll').innerHTML = '<p>No months available.</p>';
document.getElementById('monthly-calendar-controls').style.display = 'none';
return;
}
document.getElementById('monthly-calendar-controls').style.display = 'none'; // Hide global controls
let html = '';
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
html += renderStrekMonthBlock(stekId, stekData);
});
document.getElementById('monthly-calendar-scroll').innerHTML = html;
// Add event listeners for each strek's navigation
Object.keys(strekAvail).forEach(stekId => {
document.getElementById(`prev-month-btn-${stekId}`).addEventListener('click', () => {
if (strekMonthIdx[stekId] > 0) {
strekMonthIdx[stekId]--;
renderMonthlyCalendar();
}
});
document.getElementById(`next-month-btn-${stekId}`).addEventListener('click', () => {
if (strekMonthIdx[stekId] < loadedMonths.length - 1) {
strekMonthIdx[stekId]++;
renderMonthlyCalendar();
}
});
});
// Add scrollable class if needed
const strekCount = Object.keys(strekAvail).length;
const scrollDiv = document.getElementById('monthly-calendar-scroll');
if (strekCount > 2) {
scrollDiv.classList.add('scrollable');
} else {
scrollDiv.classList.remove('scrollable');
}
};
const renderStrekMonthBlock = (stekId, stekData) => {
const idx = strekMonthIdx[stekId];
const [year, month] = loadedMonths[idx].split('-').map(Number);
const availableDates = Array.isArray(stekData.available_dates) ? stekData.available_dates : [];
// Get first and last day of month
const firstDay = new Date(year, month - 1, 1);
const lastDay = new Date(year, month, 0);
const daysInMonth = lastDay.getDate();
let html = `<div class="strek-calendar-block">
<div style="font-weight:bold; margin-bottom:20px;">${stekData.label}</div>
<div style="text-align:center; margin-bottom:10px; display:flex; align-items: center; justify-content: space-between;">
<button type="button" id="prev-month-btn-${stekId}" class="prev_month"><img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/left-chevron-svgrepo-com.svg" alt="Next" width="25" height="25"> </button>
<span style="font-weight:bold; margin:0 10px;">${firstDay.toLocaleString('default', { month: 'long', year: 'numeric' })}</span>
<button type="button" id="next-month-btn-${stekId}" class="next_month"><img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/right-chevron-svgrepo-com.svg" alt="Next" width="25" height="25"></button>
</div>
<table>
<thead>
<tr>
<th>Sa</th><th>Su</th><th>Mo</th><th>Tu</th><th>We</th><th>Th</th><th>Fr</th>
</tr>
</thead>
<tbody>
<tr>`;
// Calculate the day of week for the 1st (Saturday=0)
let dayOfWeek = (firstDay.getDay() + 1) % 7;
for (let i = 0; i < dayOfWeek; i++) html += '<td></td>';
for (let day = 1; day <= daysInMonth; day++) {
if ((dayOfWeek % 7 === 0) && day !== 1) html += '</tr><tr>';
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
const isAvailable = availableDates.includes(dateStr);
const unavailableClass = isAvailable ? '' : 'unavailable';
const disabledAttr = isAvailable ? '' : 'disabled';
html += `<td>
<input type="checkbox" class="day-checkbox strek-checkbox ${unavailableClass}"
id="day-${stekId}-${year}-${month}-${day}"
data-date="${dateStr}"
data-stek="${stekId}"
${disabledAttr}>
<label for="day-${stekId}-${year}-${month}-${day}">${day}</label>
</td>`;
dayOfWeek++;
}
while (dayOfWeek % 7 !== 0) { html += '<td></td>'; dayOfWeek++; }
html += '</tr></tbody></table></div>';
return html;
};
const renderSingleMonth = (year, month) => {
// For each strek, render a classic calendar
let html = '';
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
// Get first and last day of month
const firstDay = new Date(year, month - 1, 1);
const lastDay = new Date(year, month, 0);
const daysInMonth = lastDay.getDate();
html += `<div class="strek-calendar-block">
<div style="font-weight:bold; margin-bottom:4px;">${stekData.label}</div>
<table>
<caption>${firstDay.toLocaleString('default', { month: 'long' })} ${year}</caption>
<thead>
<tr>
<th>Sa</th><th>Su</th><th>Mo</th><th>Tu</th><th>We</th><th>Th</th><th>Fr</th>
</tr>
</thead>
<tbody>
<tr>`;
// Calculate the day of week for the 1st (Saturday=0)
let dayOfWeek = (firstDay.getDay() + 1) % 7;
for (let i = 0; i < dayOfWeek; i++) html += '<td></td>';
for (let day = 1; day <= daysInMonth; day++) {
if ((dayOfWeek % 7 === 0) && day !== 1) html += '</tr><tr>';
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
html += `<td>
<input type="checkbox" class="day-checkbox strek-checkbox" id="day-${stekId}-${year}-${month}-${day}" data-date="${dateStr}" data-stek="${stekId}">
<label for="day-${stekId}-${year}-${month}-${day}">${day}</label>
</td>`;
dayOfWeek++;
}
while (dayOfWeek % 7 !== 0) { html += '<td></td>'; dayOfWeek++; }
html += '</tr></tbody></table></div>';
});
return html;
};
// TOGGLE LOGIC
document.getElementById('toggle-week').addEventListener('click', function() {
document.getElementById('toggle-week').classList.add('active');
document.getElementById('toggle-day').classList.remove('active');
document.getElementById('calendar-table-wrapper').style.display = '';
document.getElementById('monthly-calendar-wrapper').style.display = 'none';
document.getElementById('monthly-calendar-controls').style.display = 'none';
navDiv.style.display = '';
renderCalendar();
});
document.getElementById('toggle-day').addEventListener('click', function() {
document.getElementById('toggle-day').classList.add('active');
document.getElementById('toggle-week').classList.remove('active');
document.getElementById('calendar-table-wrapper').style.display = 'none';
document.getElementById('monthly-calendar-wrapper').style.display = '';
document.getElementById('monthly-calendar-controls').style.display = '';
navDiv.style.display = 'none';
renderMonthlyCalendar();
});
// Initial render
if(displayWeekly=='block'){
renderCalendar();
}else{
renderMonthlyCalendar();
}
document.getElementById('prev-month-btn').addEventListener('click', () => {
if (currentMonthIdx > 0) {
currentMonthIdx--;
renderMonthlyCalendar();
}
});
document.getElementById('next-month-btn').addEventListener('click', () => {
if (currentMonthIdx < loadedMonths.length - 1) {
currentMonthIdx++;
renderMonthlyCalendar();
}
});
document.addEventListener('change', function(e) {
if (
e.target.classList.contains('day-checkbox') &&
e.target.checked &&
!e.target.disabled
) {
// Uncheck all other day-checkboxes
document.querySelectorAll('.day-checkbox').forEach(cb => {
if (cb !== e.target) cb.checked = false;
});
const strekId = e.target.getAttribute('data-stek');
const productId = "<?php echo $product->get_id(); ?>";
const selectedDate = e.target.getAttribute('data-date');
// Set the check-in (start date) field and fire change event
const startDateInput = document.getElementById(`yith-wcbk-booking-start-date-${productId}`);
startDateInput.value = selectedDate;
// Fire both input and change events
startDateInput.dispatchEvent(new Event('input', { bubbles: true }));
startDateInput.dispatchEvent(new Event('change', { bubbles: true }));
// If a jQuery datepicker is attached, update it as well
if (typeof jQuery !== 'undefined' && jQuery(startDateInput).datepicker) {
jQuery(startDateInput).datepicker('setDate', selectedDate);
}
// Set the check-out (end date) field to one day after check-in
const endDateInput = document.getElementById(`yith-wcbk-booking-end-date-${productId}`);
if (endDateInput) {
// Calculate next day
const dateObj = new Date(selectedDate);
dateObj.setDate(dateObj.getDate() + 1);
// Format as yyyy-mm-dd
const yyyy = dateObj.getFullYear();
const mm = String(dateObj.getMonth() + 1).padStart(2, '0');
const dd = String(dateObj.getDate()).padStart(2, '0');
const nextDay = `${yyyy}-${mm}-${dd}`;
endDateInput.value = nextDay;
endDateInput.dispatchEvent(new Event('input', { bubbles: true }));
endDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(endDateInput).datepicker) {
jQuery(endDateInput).datepicker('setDate', nextDay);
}
}
// Strek dropdown logic (as before)
const select = document.querySelector(`#yith-wcbk-booking-resources-${productId} select.yith-wcbk-selector__field`);
if (select) {
select.value = strekId;
const event = new Event('change', { bubbles: true });
select.dispatchEvent(event);
const customItem = document.querySelector(`#yith-wcbk-booking-resources-${productId} .yith-wcbk-selector__item[data-key="${strekId}"]`);
if (customItem) {
customItem.click();
}
}
}
});
});
</script>
—
### ✅ **Final Working Code**
$product_id = get_the_ID(); // or pass the ID manually
$product = wc_get_product( $product_id );
// Make sure it's a booking product
if ( $product && is_a( $product, 'WC_Product_Booking' ) ) {
// Get booking form date info
$date_info = yith_wcbk_get_booking_form_date_info( $product );
// Extract important values
list(
$current_year,
$current_month,
$next_year,
$next_month,
$min_date,
$min_date_timestamp,
$max_date,
$max_date_timestamp,
$default_start_date,
$default_end_date,
$months_to_load,
$ajax_load_months,
$loaded_months
) = yith_plugin_fw_extract(
$date_info,
'current_year', 'current_month', 'next_year', 'next_month',
'min_date', 'min_date_timestamp', 'max_date', 'max_date_timestamp',
'default_start_date', 'default_end_date', 'months_to_load', 'ajax_load_months', 'loaded_months'
);
// Now you can use these in your calendar generation
}
—
### 🔒 Notes:
– Always check if the product is a `WC_Product_Booking` using `is_a()` or `instanceof`.
– `get_the_ID()` works when inside a loop or screen where global post is set.
—
Let me know if you want to show this inside a **custom meta box**, or load the data via AJAX.
> 🟥 “The **custom calendar JS script** works in frontend/backend elsewhere, but **not inside the custom Calendar tab in the WooCommerce product admin page**.”
This is common because WooCommerce tabs (like **General**, **Inventory**, your new **Calendar**) are hidden by default and only shown when selected — which can break JS initialization if you don’t handle it correctly.
—
### ✅ Step-by-Step Fix to Make Calendar JS Work in Product Admin Tab
#### 1. **Add Your Custom JS Only on Product Admin Page**
In your plugin or theme:
add_action('admin_enqueue_scripts', function($hook) {
// Only enqueue on product edit screen
if ($hook === 'post.php' || $hook === 'post-new.php') {
global $post;
if ($post && $post->post_type === 'product') {
wp_enqueue_script('my-calendar-js', plugin_dir_url(__FILE__) . 'calendar.js', ['jquery'], '1.0', true);
wp_enqueue_style('my-calendar-css', plugin_dir_url(__FILE__) . 'calendar.css');
}
}
});
—
#### 2. **Wrap Your Calendar JS Init Inside Tab Visibility Handler**
WooCommerce product tabs are hidden using `.panel` classes and only become visible when clicked.
Your JS should **wait for tab to show**, then **initialize the calendar**.
**📁 `calendar.js`** (or inline JS):
jQuery(function ($) {
function initCalendarTab() {
// Only init if calendar is not already loaded
if ($('#calendar_container').data('initialized')) return;
// 🟢 Put your calendar rendering logic here
$('#calendar_container').html('<p>Calendar will render here.</p>');
// Flag to prevent re-init
$('#calendar_container').data('initialized', true);
}
// When tab is clicked
$('a.calendar_tab').on('click', function () {
setTimeout(initCalendarTab, 100); // slight delay to allow tab DOM to render
});
// Also trigger on load if tab is already visible (e.g., refresh on Calendar tab)
if ($('#calendar_tab_data').is(':visible')) {
initCalendarTab();
}
});
—
#### 3. **Add a Container in Your Tab Panel HTML**
In your tab callback:
public function render_calendar_tab_content() {
echo '<div id="calendar_tab_data" class="panel woocommerce_options_panel">';
echo '<div id="calendar_container"></div>';
echo '</div>';
}
Make sure the ID matches `target => 'calendar_tab_data'`.
—
### ✅ Done! Your calendar will now load properly even when the tab is clicked.
Let me know if you're using a JS library like **FullCalendar** or need to pass PHP data into the JS side for rendering dates.
)
<script>
let displayWeekly='<?php echo $displayWeekly; ?>';
let displayPerday='<?php echo $displayPerday; ?>';
const strekAvail = <?php echo json_encode($strek_avail); ?>;
const strekMonthIdx = {};
Object.keys(strekAvail).forEach(stekId => {
strekMonthIdx[stekId] = 0; // Start at first month for each strek
});
const allowedDays = <?php echo json_encode($product->get_allowed_start_days()); ?>; // 1=Monday, 5=Friday, etc.
console.log('Streak Availability Data:', strekAvail);
console.log('Allowed Days:', allowedDays);
document.addEventListener('DOMContentLoaded', function() {
const weekList = <?php echo json_encode($week_list); ?>;
const weeksToShow = <?php echo $weeks_to_show; ?>;
const loadedMonths = <?php echo json_encode($loaded_months); ?>;
let weekOffset = 0;
let currentMonthIdx = 0;
const notAvailableDates = <?php echo json_encode($not_available_dates); ?>;
// Helper function to check if any day in a week is unavailable
const weekHasUnavailableDates = (weekDates, notAvailableDates) => {
for (let i = 0; i < weekDates.length; i++) {
if (notAvailableDates.includes(weekDates[i])) {
return true;
}
}
return false;
};
// WEEKLY CALENDAR
const renderCalendar = () => {
let html = `<table><thead><tr><th>Stek</th>`;
// Table headers (weeks)
for (let i = 0; i < weeksToShow; i++) {
const weekIdx = weekOffset + i;
if (weekIdx >= weekList.length) break;
const w = weekList[weekIdx];
// Find allowed weekday (e.g. Friday) in this week
let allowedDate = null;
for (let d = 0; d < w.dates.length; d++) {
const dateObj = new Date(w.dates[d]);
// getDay: 0=Sunday, 1=Monday, ..., 5=Friday, 6=Saturday
// allowedDays uses 1=Monday, 7=Sunday (ISO)
// Convert to ISO format: Saturday=6 becomes 6, Sunday=0 becomes 7
const isoDay = dateObj.getDay() === 0 ? 7 : dateObj.getDay();
if (allowedDays.includes(isoDay)) {
allowedDate = w.dates[d];
break;
}
}
let thLabel = `Week ${w.week}`;
if (allowedDate) {
const dateObj = new Date(allowedDate);
const startObj = new Date(w.dates[0]);
const endObj = new Date(w.dates[w.dates.length - 1]);
const startStr = startObj.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' });
const endStr = endObj.toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit' });
thLabel += `<br><small>${startStr} to ${endStr}</small>`;
}
html += `<th>${thLabel}</th>`;
}
html += `</tr></thead><tbody>`;
// For each stek, create a row
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
html += `<tr><td>${stekData.label}</td>`;
for (let i = 0; i < weeksToShow; i++) {
const weekIdx = weekOffset + i;
if (weekIdx >= weekList.length) break;
const w = weekList[weekIdx];
// Find allowed weekday in this week
let allowedDate = null;
for (let d = 0; d < w.dates.length; d++) {
const dateObj = new Date(w.dates[d]);
// Convert to ISO format: Saturday=6 becomes 6, Sunday=0 becomes 7
const isoDay = dateObj.getDay() === 0 ? 7 : dateObj.getDay();
if (allowedDays.includes(isoDay)) {
allowedDate = w.dates[d];
break;
}
}
// Check if this specific date is available for this specific stek
const isAvailable = allowedDate && stekData.available_dates.includes(allowedDate);
const unavailableClass = isAvailable ? '' : 'unavailable';
// Debug logging for this specific check
if (allowedDate) {
console.log(`Week ${w.week}: ${stekData.label} - Date: ${allowedDate}, Available: ${isAvailable}`);
// Show specific example for August 2nd
if (allowedDate === '2024-08-02') {
console.log(`🎯 August 2nd found! Week: ${w.week}, Resource: ${stekData.label}, Available: ${isAvailable}`);
}
}
html += `<td style="padding: 20px 10px;">
<input type="checkbox" class="week-checkbox strek-checkbox ${unavailableClass}"
id="week-check-${stekId}-${weekIdx}"
data-stek="${stekId}"
data-week="${w.week}"
data-year="${w.year}"
data-date="${allowedDate ? allowedDate : ''}"
${isAvailable ? '' : 'disabled'}>
${allowedDate
? `<br><small>${new Date(allowedDate).toLocaleDateString('nl-NL', { weekday: 'short', day: '2-digit', month: '2-digit' })}</small>`
: '<br><small>n.v.t.</small>'}
${isAvailable ? '<br><small style="color: green;">✓ Available</small>' : '<br><small style="color: red;">✗ Unavailable</small>'}
</td>`;
}
html += `</tr>`;
});
html += `</tbody></table>`;
document.getElementById('calendar-table-wrapper').innerHTML = html;
// Update navigation label
const navLabel = document.getElementById('week-navigation-label');
if (navLabel && weekList.length > 0) {
const firstWeek = weekList[weekOffset];
const lastWeek = weekList[Math.min(weekOffset + weeksToShow - 1, weekList.length - 1)];
if (firstWeek && lastWeek) {
const firstDate = new Date(firstWeek.start);
const lastDate = new Date(lastWeek.start);
lastDate.setDate(lastDate.getDate() + 6);
navLabel.textContent = `Weeks ${firstWeek.week} - ${lastWeek.week} (${firstDate.toLocaleDateString('nl-NL')} - ${lastDate.toLocaleDateString('nl-NL')})`;
}
}
// Selection highlight for checkboxes and event handlers
document.querySelectorAll('.weekly-calendar .week-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', () => {
const td = checkbox.closest('td');
if (checkbox.checked) {
// Uncheck all other week checkboxes across all streks
document.querySelectorAll('.weekly-calendar .week-checkbox').forEach(cb => {
if (cb !== checkbox) {
cb.checked = false;
cb.closest('td').classList.remove('selected');
}
});
td.classList.add('selected');
// Update the booking form fields
const strekId = checkbox.getAttribute('data-stek');
const productId = "<?php echo $product->get_id(); ?>";
const selectedDate = checkbox.getAttribute('data-date');
// Set the check-in (start date) field
const startDateInput = document.getElementById(`yith-wcbk-booking-start-date-${productId}`);
if (startDateInput) {
startDateInput.value = selectedDate;
startDateInput.dispatchEvent(new Event('input', { bubbles: true }));
startDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(startDateInput).datepicker) {
jQuery(startDateInput).datepicker('setDate', selectedDate);
}
}
// Set the check-out (end date) field to 7 days after check-in
const endDateInput = document.getElementById(`yith-wcbk-booking-end-date-${productId}`);
if (endDateInput) {
const dateObj = new Date(selectedDate);
dateObj.setDate(dateObj.getDate() + 7); // 7 days (week)
const yyyy = dateObj.getFullYear();
const mm = String(dateObj.getMonth() + 1).padStart(2, '0');
const dd = String(dateObj.getDate()).padStart(2, '0');
const endDate = `${yyyy}-${mm}-${dd}`;
endDateInput.value = endDate;
endDateInput.dispatchEvent(new Event('input', { bubbles: true }));
endDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(endDateInput).datepicker) {
jQuery(endDateInput).datepicker('setDate', endDate);
}
}
// Update strek dropdown
const select = document.querySelector(`#yith-wcbk-booking-resources-${productId} select.yith-wcbk-selector__field`);
if (select) {
select.value = strekId;
const event = new Event('change', { bubbles: true });
select.dispatchEvent(event);
const customItem = document.querySelector(`#yith-wcbk-booking-resources-${productId} .yith-wcbk-selector__item[data-key="${strekId}"]`);
if (customItem) {
customItem.click();
}
}
} else {
td.classList.remove('selected');
}
});
});
};
document.getElementById('prev-week-btn')?.remove();
document.getElementById('next-week-btn')?.remove();
const navDiv = document.createElement('div');
navDiv.style.display = "flex";
navDiv.style.justifyContent = "center";
navDiv.style.marginBottom = "10px";
navDiv.innerHTML = `
<button type="button" id="prev-week-btn" style="display:<?php echo $displayWeekly; ?>;"> <img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/left-chevron-svgrepo-com.svg" alt="Previous" width="25" height="25"> </button>
<div style="flex: 1; text-align: center; font-weight: bold;display:<?php echo $displayWeekly; ?>;" id="week-navigation-label">Select a week</div>
<button type="button" id="next-week-btn" style="display:<?php echo $displayWeekly; ?>;"> <img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/right-chevron-svgrepo-com.svg" alt="Previous" width="25" height="25"> </button>
`;
document.getElementById('calendar-table-wrapper').before(navDiv);
document.getElementById('prev-week-btn').addEventListener('click', () => {
if (weekOffset - weeksToShow >= 0) {
weekOffset -= weeksToShow;
} else {
weekOffset = 0;
}
renderCalendar();
});
document.getElementById('next-week-btn').addEventListener('click', () => {
if (weekOffset + weeksToShow < weekList.length) {
weekOffset += weeksToShow;
renderCalendar();
}
});
// MONTHLY CALENDAR (one month at a time)
const renderMonthlyCalendar = () => {
if (loadedMonths.length === 0) {
document.getElementById('monthly-calendar-scroll').innerHTML = '<p>No months available.</p>';
document.getElementById('monthly-calendar-controls').style.display = 'none';
return;
}
document.getElementById('monthly-calendar-controls').style.display = 'none'; // Hide global controls
let html = '';
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
html += renderStrekMonthBlock(stekId, stekData);
});
document.getElementById('monthly-calendar-scroll').innerHTML = html;
// Add event listeners for each strek's navigation
Object.keys(strekAvail).forEach(stekId => {
document.getElementById(`prev-month-btn-${stekId}`).addEventListener('click', () => {
if (strekMonthIdx[stekId] > 0) {
strekMonthIdx[stekId]--;
renderMonthlyCalendar();
}
});
document.getElementById(`next-month-btn-${stekId}`).addEventListener('click', () => {
if (strekMonthIdx[stekId] < loadedMonths.length - 1) {
strekMonthIdx[stekId]++;
renderMonthlyCalendar();
}
});
});
// Add scrollable class if needed
const strekCount = Object.keys(strekAvail).length;
const scrollDiv = document.getElementById('monthly-calendar-scroll');
if (strekCount > 2) {
scrollDiv.classList.add('scrollable');
} else {
scrollDiv.classList.remove('scrollable');
}
};
const renderStrekMonthBlock = (stekId, stekData) => {
const idx = strekMonthIdx[stekId];
const [year, month] = loadedMonths[idx].split('-').map(Number);
const availableDates = Array.isArray(stekData.available_dates) ? stekData.available_dates : [];
// Get first and last day of month
const firstDay = new Date(year, month - 1, 1);
const lastDay = new Date(year, month, 0);
const daysInMonth = lastDay.getDate();
let html = `<div class="strek-calendar-block">
<div style="font-weight:bold; margin-bottom:20px;">${stekData.label}</div>
<div style="text-align:center; margin-bottom:10px; display:flex; align-items: center; justify-content: space-between;">
<button type="button" id="prev-month-btn-${stekId}" class="prev_month"><img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/left-chevron-svgrepo-com.svg" alt="Next" width="25" height="25"> </button>
<span style="font-weight:bold; margin:0 10px;">${firstDay.toLocaleString('default', { month: 'long', year: 'numeric' })}</span>
<button type="button" id="next-month-btn-${stekId}" class="next_month"><img src="https://carplakes.newsoftdemo.info/wp-content/uploads/2025/07/right-chevron-svgrepo-com.svg" alt="Next" width="25" height="25"></button>
</div>
<table>
<thead>
<tr>
<th>Sa</th><th>Su</th><th>Mo</th><th>Tu</th><th>We</th><th>Th</th><th>Fr</th>
</tr>
</thead>
<tbody>
<tr>`;
// Calculate the day of week for the 1st (Saturday=0)
let dayOfWeek = (firstDay.getDay() + 1) % 7;
for (let i = 0; i < dayOfWeek; i++) html += '<td></td>';
for (let day = 1; day <= daysInMonth; day++) {
if ((dayOfWeek % 7 === 0) && day !== 1) html += '</tr><tr>';
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
const isAvailable = availableDates.includes(dateStr);
const unavailableClass = isAvailable ? '' : 'unavailable';
const disabledAttr = isAvailable ? '' : 'disabled';
html += `<td>
<input type="checkbox" class="day-checkbox strek-checkbox ${unavailableClass}"
id="day-${stekId}-${year}-${month}-${day}"
data-date="${dateStr}"
data-stek="${stekId}"
${disabledAttr}>
<label for="day-${stekId}-${year}-${month}-${day}">${day}</label>
</td>`;
dayOfWeek++;
}
while (dayOfWeek % 7 !== 0) { html += '<td></td>'; dayOfWeek++; }
html += '</tr></tbody></table></div>';
return html;
};
const renderSingleMonth = (year, month) => {
// For each strek, render a classic calendar
let html = '';
Object.entries(strekAvail).forEach(([stekId, stekData]) => {
// Get first and last day of month
const firstDay = new Date(year, month - 1, 1);
const lastDay = new Date(year, month, 0);
const daysInMonth = lastDay.getDate();
html += `<div class="strek-calendar-block">
<div style="font-weight:bold; margin-bottom:4px;">${stekData.label}</div>
<table>
<caption>${firstDay.toLocaleString('default', { month: 'long' })} ${year}</caption>
<thead>
<tr>
<th>Sa</th><th>Su</th><th>Mo</th><th>Tu</th><th>We</th><th>Th</th><th>Fr</th>
</tr>
</thead>
<tbody>
<tr>`;
// Calculate the day of week for the 1st (Saturday=0)
let dayOfWeek = (firstDay.getDay() + 1) % 7;
for (let i = 0; i < dayOfWeek; i++) html += '<td></td>';
for (let day = 1; day <= daysInMonth; day++) {
if ((dayOfWeek % 7 === 0) && day !== 1) html += '</tr><tr>';
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
html += `<td>
<input type="checkbox" class="day-checkbox strek-checkbox" id="day-${stekId}-${year}-${month}-${day}" data-date="${dateStr}" data-stek="${stekId}">
<label for="day-${stekId}-${year}-${month}-${day}">${day}</label>
</td>`;
dayOfWeek++;
}
while (dayOfWeek % 7 !== 0) { html += '<td></td>'; dayOfWeek++; }
html += '</tr></tbody></table></div>';
});
return html;
};
// TOGGLE LOGIC
document.getElementById('toggle-week').addEventListener('click', function() {
document.getElementById('toggle-week').classList.add('active');
document.getElementById('toggle-day').classList.remove('active');
document.getElementById('calendar-table-wrapper').style.display = '';
document.getElementById('monthly-calendar-wrapper').style.display = 'none';
document.getElementById('monthly-calendar-controls').style.display = 'none';
navDiv.style.display = '';
renderCalendar();
});
document.getElementById('toggle-day').addEventListener('click', function() {
document.getElementById('toggle-day').classList.add('active');
document.getElementById('toggle-week').classList.remove('active');
document.getElementById('calendar-table-wrapper').style.display = 'none';
document.getElementById('monthly-calendar-wrapper').style.display = '';
document.getElementById('monthly-calendar-controls').style.display = '';
navDiv.style.display = 'none';
renderMonthlyCalendar();
});
// Initial render
if(displayWeekly=='block'){
renderCalendar();
}else{
renderMonthlyCalendar();
}
document.getElementById('prev-month-btn').addEventListener('click', () => {
if (currentMonthIdx > 0) {
currentMonthIdx--;
renderMonthlyCalendar();
}
});
document.getElementById('next-month-btn').addEventListener('click', () => {
if (currentMonthIdx < loadedMonths.length - 1) {
currentMonthIdx++;
renderMonthlyCalendar();
}
});
document.addEventListener('change', function(e) {
if (
e.target.classList.contains('day-checkbox') &&
e.target.checked &&
!e.target.disabled
) {
// Uncheck all other day-checkboxes
document.querySelectorAll('.day-checkbox').forEach(cb => {
if (cb !== e.target) cb.checked = false;
});
const strekId = e.target.getAttribute('data-stek');
const productId = "<?php echo $product->get_id(); ?>";
const selectedDate = e.target.getAttribute('data-date');
// Set the check-in (start date) field and fire change event
const startDateInput = document.getElementById(`yith-wcbk-booking-start-date-${productId}`);
startDateInput.value = selectedDate;
// Fire both input and change events
startDateInput.dispatchEvent(new Event('input', { bubbles: true }));
startDateInput.dispatchEvent(new Event('change', { bubbles: true }));
// If a jQuery datepicker is attached, update it as well
if (typeof jQuery !== 'undefined' && jQuery(startDateInput).datepicker) {
jQuery(startDateInput).datepicker('setDate', selectedDate);
}
// Set the check-out (end date) field to one day after check-in
const endDateInput = document.getElementById(`yith-wcbk-booking-end-date-${productId}`);
if (endDateInput) {
// Calculate next day
const dateObj = new Date(selectedDate);
dateObj.setDate(dateObj.getDate() + 1);
// Format as yyyy-mm-dd
const yyyy = dateObj.getFullYear();
const mm = String(dateObj.getMonth() + 1).padStart(2, '0');
const dd = String(dateObj.getDate()).padStart(2, '0');
const nextDay = `${yyyy}-${mm}-${dd}`;
endDateInput.value = nextDay;
endDateInput.dispatchEvent(new Event('input', { bubbles: true }));
endDateInput.dispatchEvent(new Event('change', { bubbles: true }));
if (typeof jQuery !== 'undefined' && jQuery(endDateInput).datepicker) {
jQuery(endDateInput).datepicker('setDate', nextDay);
}
}
// Strek dropdown logic (as before)
const select = document.querySelector(`#yith-wcbk-booking-resources-${productId} select.yith-wcbk-selector__field`);
if (select) {
select.value = strekId;
const event = new Event('change', { bubbles: true });
select.dispatchEvent(event);
const customItem = document.querySelector(`#yith-wcbk-booking-resources-${productId} .yith-wcbk-selector__item[data-key="${strekId}"]`);
if (customItem) {
customItem.click();
}
}
}
});
});
</script>
—
### ✅ 1. Add a Custom Tab to Product Admin
add_filter('woocommerce_product_data_tabs', function($tabs) {
$tabs['calendar_tab'] = [
'label' => __('Calendar', 'your-textdomain'),
'target' => 'calendar_tab_data',
'class' => ['show_if_booking'], // Only show for booking product
'priority' => 99,
];
return $tabs;
});
—
### ✅ 2. Add the Calendar Tab Content Panel
add_action('woocommerce_product_data_panels', function() {
echo '<div id="calendar_tab_data" class="panel woocommerce_options_panel">';
echo '<div id="calendar_container">Loading calendar…</div>';
echo '</div>';
});
—
### ✅ 3. Enqueue JS and Init on Tab Show
add_action('admin_enqueue_scripts', function($hook) {
if (in_array($hook, ['post.php', 'post-new.php'])) {
global $post;
if ($post && $post->post_type === 'product') {
wp_enqueue_script('admin-calendar', plugin_dir_url(__FILE__) . 'admin-calendar.js', ['jquery'], null, true);
wp_localize_script('admin-calendar', 'calendarData', [
'ajax_url' => admin_url('admin-ajax.php'),
'product_id' => $post->ID,
]);
}
}
});
—
### ✅ 4. JavaScript (`admin-calendar.js`)
jQuery(function ($) {
function initCalendarTab() {
if ($('#calendar_container').data('initialized')) return;
$('#calendar_container').html('<p>Custom calendar logic here for product ID: ' + calendarData.product_id + '</p>');
$('#calendar_container').data('initialized', true);
}
// Load when Calendar tab is clicked
$('a.calendar_tab').on('click', function () {
setTimeout(initCalendarTab, 150); // Slight delay
});
// If already visible (e.g. refresh on Calendar tab)
if ($('#calendar_tab_data').is(':visible')) {
initCalendarTab();
}
});
—
### ✅ 5. Optional Styling
add_action('admin_enqueue_scripts', function($hook) {
if (in_array($hook, ['post.php', 'post-new.php'])) {
wp_enqueue_style('admin-calendar-style', plugin_dir_url(__FILE__) . 'admin-calendar.css');
}
});
—
### ✅ Final Tips
– Make sure your calendar logic (e.g. FullCalendar, Week view) runs inside the `initCalendarTab()` function.
– You can now use `calendarData.product_id` to fetch dynamic data via AJAX if needed.
Let me know if you want to fetch actual **resource availability** dynamically in the admin calendar.
—
### ✅ Step-by-Step Implementation with Inline JS
#### 1. **Add the custom Calendar tab**
add_filter('woocommerce_product_data_tabs', function($tabs) {
$tabs['calendar_tab'] = [
'label' => __('Calendar', 'your-textdomain'),
'target' => 'calendar_tab_data',
'class' => ['show_if_booking'], // Only for booking products
'priority' => 99,
];
return $tabs;
});
—
#### 2. **Add the content panel + inline JS**
add_action('woocommerce_product_data_panels', function() {
global $post;
$product_id = $post->ID;
echo '<div id="calendar_tab_data" class="panel woocommerce_options_panel">';
echo '<div id="calendar_container">Loading calendar…</div>';
// Inline JS to initialize when the tab is clicked
echo "<script>
jQuery(function($) {
function initCalendarTab() {
if ($('#calendar_container').data('initialized')) return;
// Your custom calendar logic goes here
$('#calendar_container').html('<p>Calendar for product ID: {$product_id}</p>');
$('#calendar_container').data('initialized', true);
}
// When Calendar tab is clicked
$('a.calendar_tab').on('click', function () {
setTimeout(initCalendarTab, 150);
});
// Also init if visible on load (e.g. refresh on Calendar tab)
if ($('#calendar_tab_data').is(':visible')) {
initCalendarTab();
}
});
</script>";
echo '</div>';
});
—
### ✅ Result:
– Adds a new **"Calendar"** tab on the product edit screen.
– When clicked, it runs the JS **inline** and populates the `#calendar_container`.
– This works without external JS files and handles the WooCommerce hidden-tab DOM issue.
—
Let me know if you want to render a full calendar layout, date table, or load availability via PHP/AJAX in that tab.