Check out the latest example on the Velo Examples page, based on the new wix-booking-backend APIs.
Query and display a list of bookings filtered by date and status.
Important forum update
This forum is migrating to one unified Wix community forum starting July 26th, and will be read-only during the process.
Wishlist Page is the official platform for requesting new features. You can vote, comment, and track the status of the requested features.
Hello - I am having a hard time grabbing the Booking ID to add to my repeater
In this code segment from the example -
$w('#sessionsRepeater').onItemReady(($item, data) => { $item('#sessionDateText').text = data.bookedEntity.singleSession.start.toLocaleString(); $item('#sessionClientNameText').text = data.formInfo.contactDetails.firstName; $item('#sessionServiceText').text = data.bookedEntity.title; $item('#sessionStatusText').text = data.status; }); $w('#sessionsRepeater').data = [];
If I try to add a booking ID
This doesn't work -
$item('#sessionIdText').text = data._id;
This also doesn't work -
$item('#sessionIdText').text = data.BookingId;
What do I need to call?
Thanks - I am VERY new to this, but I've tried everything
Had a go with the suggestion above attempting to retrieve the staff members and display them in the status field instead for each booking. not getting any change here but attaching the code below. Any suggestions?
import { getBookings } from 'backend/bookings.jsw'; import wixUsers from 'wix-users'; import wixData from 'wix-data'; let staffMap = {}; $w.onReady(function () { initElements(); }); async function initializeElements() { // Get all the staff from the Staff collection. const staff = await getAllStaff(); // Put the staff members in a map for easy access by ID. staff.forEach(member => staffMap[member._id] = member); } async function getAllStaff() { const data = await wixData.query("Bookings/Staff").find(); return data.items; } 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", "CANCELED"]; $w('#dateStart').value = new Date("2021-11-01T17:00:00.000Z"); $w('#dateEnd').value = new Date("2022-05-08T17:00:00.000Z"); $w('#sessionsRepeater').onItemReady(($item, data, index) => { $item('#sessionDateText').text = data.bookedEntity.singleSession.start.toLocaleString(); $item('#sessionClientNameText').text = data.formInfo.contactDetails.firstName; $item('#sessionServiceText').text = data.bookedEntity.title; const staffMember = staffMap[data.staffMemberId].name; $item('#sessionStaffText').text = staffMember; }); $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; } }
Thanks for this tutorial, really helpful.
How would i go about displaying the Staff member associated with the bookings in the repeater?
and would i need to be filtering in the backend code if i wanted to filter by staff member as an additional feature?
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.
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
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.
@Moran Frumer Sorry Moran this got way over my head :( 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!
@JordOnDrums 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 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.
@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.
@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!
@JordOnDrums 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 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.
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).
So it is. A few days ago I noticed the API works correctly and my webapp is okay now. Thanks for fix the issue.
@Mauricio Vanegas we fixed this issue, the API should work as expected
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.