New Example: Booking List

Check out the latest example on the Velo Examples page , based on the new wix-booking-backend APIs.

Booking List
Query and display a list of bookings filtered by date and status.

Hi.

I basically copied and pasted the example code in my website (I did not change anything), and it works in the preview mode perfectly. However, in the live site, it does not work and catches these messages to the console:

‘Wix code SDK error: The data parameter that is passed to the data method cannot be set to the value undefined. It must be of type array.’

‘loadBookings error - Cannot read property ‘length’ of undefined’

May someone help me? The URL is wedyco.com/citas-1

Thanks.

We are checking it.

@mauvanceb we fixed this issue, the API should work as expected

1 Like

So it is. A few days ago I noticed the API works correctly and my webapp is okay now. Thanks for fix the issue.

Is it possible to filter the query by priceAmount , ascending? (I’ve looked through the Bookings api and this doesn’t seem to be accounted for).

Price is not part of the current filters. If the total number of bookings in not too large (few 100s should be ok) then a possible workaround can be to retrieve all the bookings items and then filter the results array based on the price.

@moran-frumer Is it possible to filter by if the booking has attendees already? For example if you wanted to create a page to show a filtered list for a group session that only shows sessions that have at least one student so far? Allowing people to join a group session already established.

@jordon It is possible with some array manipulations. In the below example:
Get all bookings with status “CONFIRMED”
Filter only group session (in case there are appointments in the list)
Get a list of unique classes, class name and time

const res = await bookings . queryBookings ()
. ge ( “startTime” , startDate )
. le ( “endTime” , endDate )
. eq ( “status” , “CONFIRMED” )
. limit ( 1000 )
. find ({ suppressAuth : true });
const classesOnly = res . items . filter ( book => book . bookedEntity . tags [ 0 ]=== “GROUP” )
return [… new Set ( classesOnly . map ( book => book . bookedEntity . singleSession . sessionId ))]
. map ( sessionId =>{
const sessionInfo = classesOnly . find ( book => book . bookedEntity . singleSession . sessionId == sessionId );
return {
“className” : sessionInfo . bookedEntity . title ,
“startTime” : sessionInfo . bookedEntity . singleSession . start . toDateString (),
“sessionId” : sessionId
}
})

@moran-frumer Thank you so much I didn’t expect you to write it all out for me but tank you I really appreciate it. I didn’t think of this! I poured through the API’s and tried to think of a way to make it happen but you nailed it. Thanks so much!

1 Like

@moran-frumer I got it working except I am trying to display the current number of registered attendees. in a text box then it’s all complete.

$item('#totalAttendees').text = data.attendanceInfo.numberOfAttendees.toLocaleString();

This is normally no problem at all but for some reason I just can’t manage to pull that information. Am I missing something?

I am able to pull the title and start time using the following;

$item('#sessionDateText').text = data.bookedEntity.singleSession.start.toLocaleString();

$item('#sessionServiceText').text = data.bookedEntity.title;

Just not the current number of attendees or participants registered for the group.

Thanks so much in advance and sorry to bother you on this.

@jordon
This should work.
const participantCount= classesOnly . filter ( book => book . bookedEntity . singleSession . sessionId == sessionId ).length;

@moran-frumer Thanks! The question now becomes how do I write that to a text box to display in the table. Even if I put;

$item ( ‘#totalAttendees’ ). text =participantCount

I get an error.

@jordon Run the participant count next the the sessionInfo and make sure to return the value to front

const sessionInfo = classesOnly . find ( book => book . bookedEntity . singleSession . sessionId == sessionId );
const participantCount = classesOnly . filter ( book => book . bookedEntity . singleSession . sessionId == sessionId ). length ;
return {
“className” : sessionInfo . bookedEntity . title ,
“startTime” : sessionInfo . bookedEntity . singleSession . start . toDateString (),
“sessionId” : sessionId ,
“participantCount” : participantCount
}

@moran-frumer Sorry Moran this got way over my head :frowning:
Here is my full code, Front end and Backend

I highlighted the spot I am stuck on pulling besides that it is done. I used the code you gave me to sort.

import { getBookings } from 'backend/bookings.jsw';
import wixUsers from 'wix-users';

$w.onReady(function () {
    initElements();
});

function initElements() {

    $w('#submitButton').onClick(() => loadBookings());

    $w('#statusCheckbox').options = [
        { "value": "CONFIRMED", "label": "CONFIRMED" },
        { "value": "CANCELED", "label": "CANCELED" },
        { "value": "PENDING", "label": "PENDING" },
        { "value": "PENDING_CHECKOUT", "label": "PENDING CHECKOUT" },
        { "value": "PENDING_APPROVAL", "label": "PENDING APPROVAL" },
        { "value": "DECLINED", "label": "DECLINED" }
    ];

    $w('#statusCheckbox').value = ["CONFIRMED"];
    $w('#dateStart').value = new Date();
    $w('#dateEnd').value = new Date();

    $w('#sessionsRepeater').onItemReady(($item, data) => {
        $item('#sessionDateText').text = data.bookedEntity.singleSession.start.toLocaleString();
        $item('#sessionServiceText').text = data.bookedEntity.title;
        $item('#sessionStatusText').text = data.status;
        $item('#totalAttendees').text = data.participantCount


    });
    $w('#sessionsRepeater').data = [];
}
async function loadBookings() {

    $w('#errorText').hide();
    if (!($w('#dateStart').valid && $w('#dateEnd').valid && $w('#statusCheckbox').valid)) {
        $w('#errorText').show();
        $w('#errorText').text = "Error in form fields";
        return;
    }

    // Remove the comment to limit access only to Admin members (recommended for security best practices)
    /* if(!await isUserAdmin()) {
        $w('#errorText').show();
        $w('#errorText').text = "This data is accessible to admin members only";
        return;
    } */

    $w('#submitButton').disable();
    const dateStart = $w('#dateStart').value;
    const dateEndTemp = $w('#dateEnd').value;
    const dateEnd = new Date(dateEndTemp.getTime() + 60 * 60 * 24 * 1000); // adding one day to the end date so bookings on that date also pass the filter
    const statuses = $w('#statusCheckbox').value;

    getBookings(dateStart, dateEnd, statuses).then((results) => {
        $w('#sessionsRepeater').data = results;
        if (results.length == 0) {
            $w('#errorText').show();
            $w('#errorText').text = "No bookings found";
        } else {
            $w('#titles').show()
        }
    }).catch((error) => {
        console.error('loadBookings error - ' + error.message);
    }).finally(() => {
        $w('#submitButton').enable();
    });
}

async function isUserAdmin() {
    try {

        let user = wixUsers.currentUser;
        if (!user.loggedIn) {
            console.log('Member is not logged in');
            return false;
        }

        let roles = await user.getRoles();
        if (roles.find(role => role.name == "Admin")) {
            return true;
        } else {
            console.log('Member is not an Admin');
            return false;
        }

    } catch (error) {
        console.error('isUserAdmin error - ' + error.message);
        return false;
    }
}

Here is the back end



import { bookings } from "wix-bookings-backend";
import wixUsers from 'wix-users-backend';

let options = {
    suppressAuth: true
}

async function isUserAdmin() {
    try {
        let user = wixUsers.currentUser;
        if (!user.loggedIn) {
            return false;
        }
        let roles = await user.getRoles();
        if (roles.find(role => role.name == 'Admin')) {
            return true;
        } else {
            return false;
        }
    } catch (error) {
        console.error('bookings.jsw > isUserAdmin - ' + error.message);
        return false;
    }
}

export async function getBookings(dateStart, dateEnd, statuses) {

    // Remove the comment to limit access only to Admin members (recommended for security best practices)
    /* if(!await isUserAdmin()) {
        console.log('bookings.jsw > getBookings - Non-admin member is not allowed to run this function');
        return [];
    } */

    return bookings
        .queryBookings()
        .hasSome("status", statuses)
        .ge("startTime", dateStart)
        .le("endTime", dateEnd)
        .ascending("startTime")
        .find(options)
        .then((queryResult) => {
            return queryResult.items;
        })
        .catch((error) => {
            Promise.reject(
                new Error('booking.jsw > getBookings error - details -' + error.message));
        });
}

Not sure what I am missing sorry!

@jordon The backend func, getBookings, in your code returns a list of all bookings (this was the purpose of the example).

My code snippet need to run on the backend, in a different function and it returns a list of group sessions that have at least one registered participant.

Hi, I want disable possibility to cancel of the appointment by customers from the page of booking list in member area, so I have created the own page to replace the list of booking and display a list, but that one is without links to reschedule, cancel and book again. I want that customer can reschedule theirs bookings (only), but I not want create my own booking lifecycle. I looking for a solution that allow me direct forward a customer to the same place when customer is forwarded by the link from the original booking list.

Hi Robert,
We are currently working on a new Wix app that will allow anonymous cancel and reschedules, this will give you the option to split the policies time and give the client a page that will allow him (or not) to cancel/reschedule. It will be released in a few weeks.
If you still want to implement it now, I would suggest creating a dynamic page that will be created for every book, the link for the dynamic page (unique per book) will be sent in the confirmation email (triggered email) to the customer, once the customer opens the page the code will check the policy of cancel / reschedule and show cancel/reschedule button accordingly, once the customer clicks on the relevant button the button will initiate the request for the specific book (cancel or reschedule).
Hope it helped, if not share your email and I will DM you.
Zvi

1 Like

Hi Zvi and thanks for the response.

My issue is I don’t know how to create the URL for a particular booking to reschedule it.

The page of customer bookings in the member area (that list with two tabs: upcoming and history) contains for every booking links to reschedule and cancel because they are part of the Wix Booking component.

What I did was hide the default page (that one from above) created when installing the Wix Bookings application and create a new one.

I have my custom application in Wix Developers - My Apps. The application is integrated with the Wix by the REST API and has the page component which underhood fetches booking for a particular customer (that one logged-in member area) and display them like the original component of the Wix Bookings except the links to reschedule and cancel.

For example, when I fetched the list of services by REST API (POST www.wixapis. com/bookings/v1/catalog/services/query), I got services with URLs of “servicePageUrl” and “bookingPageUrl”, so it allows me to add links to these pages where I will need it.

Querying REST API (POST www.wixapis. com/bookings/v1/bookings/query) for bookings I don’t get such URLs as for services, so I can not generate a button that redirects users to a page where they can reschedule the booking.

Here is my email: rob.wachal @ gmail .com if you would like to respond.

Thanks for the explanation, replied to the specified email