vue2-booking-calendar

A customizable booking calendar component for Vue 2 with service, specialist, and customer management
5,381
Install
npm install vue2-booking-calendar
Latest Version:2.8.0
Vue:2
License:MIT
Last Updated:Jul 17, 2026
Links: npm
Maintainer: meemawsh

vue2-booking-calendar

A customizable booking calendar component for Vue 2 with service, specialist, and customer management.

Features

  • Month, week, and day views
  • Booking creation with bidirectional cascading dropdowns (brand ↔ branch ↔ service ↔ specialist ↔ customer)
  • Select any field first — all other dropdowns filter automatically
  • View booking details with full customer info (name, email, phone, address, notes)
  • No-show and cancel booking with confirmation dialogs
  • Specialist availability side panel with booked slot detection
  • New customer inline creation from booking form
  • Per-service duration with auto-computed end times
  • Customizable business hours (standard, 24h, and overnight schedules)
  • Event callbacks with custom function editors and live log
  • Full JSON configuration for dropdown options and sample events
  • Drag and drop events with 15-minute snap granularity
  • SVG icons throughout
  • Fully customizable via slots

Installation

npm install vue2-booking-calendar

Quick Start

import Vue from 'vue'
import Vuex from 'vuex'
import BookingCalendar, { storeConfig } from 'vue2-booking-calendar'
import 'vue2-booking-calendar/dist/style.css'

Vue.use(Vuex)
Vue.use(BookingCalendar)

const store = new Vuex.Store(storeConfig)

new Vue({
  store,
  template: '<BookingCalendar />'
}).$mount('#app')

Initializing with Custom Data

Pass your own brands, branches, services, specialists, and customers at setup:

const store = new Vuex.Store({
  ...storeConfig,
  state() {
    const state = storeConfig.state()

    // Set your dropdown data
    state.config.dropdownOptions.brand = [
      { uuid: 'b1', name: 'My Salon' },
      { uuid: 'b2', name: 'My Spa' }
    ]

    state.config.dropdownOptions.branch = [
      { uuid: 'br1', brand_uuid: 'b1', name: 'Main Street' },
      { uuid: 'br2', brand_uuid: 'b1', name: 'Mall Location' },
      { uuid: 'br3', brand_uuid: 'b2', name: 'Downtown' }
    ]

    state.config.dropdownOptions.service = [
      { uuid: 'sv1', name: 'Haircut', duration: 60, brands: ['b1'], branches: ['br1', 'br2'], specialists: ['sp1', 'sp2'] },
      { uuid: 'sv2', name: 'Massage', duration: 90, brands: ['b2'], branches: ['br3'], specialists: ['sp3'] }
    ]

    state.config.dropdownOptions.specialist = [
      { uuid: 'sp1', name: 'Jane Smith', services: ['sv1'], brand_uuid: 'b1', branch_uuid: 'br1', availability: { monday: ['09:00','09:30','10:00'], tuesday: ['09:00','09:30'], wednesday: [], thursday: [], friday: [], saturday: [], sunday: [] } },
      { uuid: 'sp2', name: 'Bob Jones', services: ['sv1'], brand_uuid: 'b1', branch_uuid: 'br2', availability: { monday: ['10:00','10:30','11:00'], tuesday: [], wednesday: ['09:00','09:30'], thursday: [], friday: [], saturday: [], sunday: [] } },
      { uuid: 'sp3', name: 'Dr. Lee', services: ['sv2'], brand_uuid: 'b2', branch_uuid: 'br3', availability: { monday: ['14:00','14:30','15:00'], tuesday: ['14:00','14:30'], wednesday: [], thursday: [], friday: [], saturday: [], sunday: [] } }
    ]

    state.config.dropdownOptions.customer = [
      { uuid: 'c1', name: 'Alice Brown', email: 'alice@example.com', phone: '+1 555-0101', gender: 'Female', address: '123 Oak St', notes: '' },
      { uuid: 'c2', name: 'Charlie Davis', email: 'charlie@example.com', phone: '+1 555-0102', gender: 'Male', address: '', notes: 'VIP' }
    ]

    state.config.dropdownOptions.time = [
      '09:00', '09:30', '10:00', '10:30', '11:00', '11:30',
      '12:00', '12:30', '13:00', '13:30', '14:00', '14:30',
      '15:00', '15:30', '16:00', '16:30', '17:00'
    ]

    // Clear sample events (start empty)
    state.events = []

    return state
  }
})

Loading Data from API

// After store is created, fetch and set data dynamically
async function loadCalendarData() {
  const [brands, branches, services, specialists, customers] = await Promise.all([
    fetch('/api/brands').then(r => r.json()),
    fetch('/api/branches').then(r => r.json()),
    fetch('/api/services').then(r => r.json()),
    fetch('/api/specialists').then(r => r.json()),
    fetch('/api/customers').then(r => r.json())
  ])

  store.commit('SET_CONFIG', { key: 'dropdownOptions.brand', value: brands })
  store.commit('SET_CONFIG', { key: 'dropdownOptions.branch', value: branches })
  store.commit('SET_CONFIG', { key: 'dropdownOptions.service', value: services })
  store.commit('SET_CONFIG', { key: 'dropdownOptions.specialist', value: specialists })
  store.commit('SET_CONFIG', { key: 'dropdownOptions.customer', value: customers })

  // Load existing bookings
  const events = await fetch('/api/bookings').then(r => r.json())
  store.commit('SET_EVENTS', events)
}

loadCalendarData()

Configuration

All configuration is managed through the Vuex store config object. You can override defaults before creating the store:

const store = new Vuex.Store({
  ...storeConfig,
  state() {
    const state = storeConfig.state()
    // Override config
    state.config.businessHoursStart = 9
    state.config.businessHoursEnd = 17
    state.config.timeFormat = '24h'
    state.config.showLogo = false
    return state
  }
})

Config Options

Option Type Default Description
slotDuration number 60 Time slot duration in minutes (15, 30, 60)
businessHoursStart number 8 Business hours start (0-23)
businessHoursEnd number 18 Business hours end (0-23)
is24HourOperation boolean false Enable 24-hour operation (disables business hours filtering)
timeFormat string '12h' Time display format ('12h' or '24h')
firstDayOfWeek number 0 First day of week (0 = Sunday, 1 = Monday)
showWeekends boolean true Show weekend columns
maxVisibleEvents number 3 Max events visible per day in month view
defaultEventDuration number 60 Fallback duration when service has no duration
sidebarPosition string 'left' Mini calendar sidebar position ('left', 'right', 'none')
showHeader boolean true Show calendar navigation header
showLogo boolean true Show the logo header bar
showCustomizationPanel boolean true Show the customization settings panel
showManageButton boolean true Show the Manage button in the view booking footer
showCompleteButton boolean true Show the Complete action in the Actions dropdown
showCancelButton boolean true Show the Cancel Booking action in the Actions dropdown
showViewEventModal boolean true Show the view booking dialog when clicking an event (callback still fires when disabled)
maxVisibleEvents number 3 Max events visible per hour slot in week/day views (overflow shows "+N more" badge)
allowPastDateBooking boolean false Allow creating bookings on past dates
actionsPosition string 'left' Actions dropdown position in footer ('left', 'center', 'right')
buttonRadius string 'rounded' Button border-radius style ('square', 'rounded', 'pill')
showSelectCustomer boolean true Show existing customer dropdown in booking form
showNewCustomer boolean true Show new customer inline form in booking form
showCurrentTime boolean true Show current time indicator line
highlightToday boolean true Highlight today's column
eventBorderRadius string 'rounded' Event card style ('rounded' or 'square')
dayStartHour number 0 Hour to start the day view from

Booking Field Visibility

Control which fields appear in the booking form and view modal:

bookingFields: {
  brand: true,       // Show in create/edit form
  branch: true,
  service: true,
  date: true,
  time: true,
  specialist: true,
  customer: true,
  color: true
}

viewFields: {
  brand: true,       // Show in view booking modal
  branch: true,
  service: true,
  date: true,
  time: true,
  specialist: true,
  customer: true
}

Business Hours

Standard schedule (e.g. 8 AM – 6 PM):

businessHoursStart: 8,
businessHoursEnd: 18,
is24HourOperation: false

Overnight schedule (e.g. 8 PM – 5 AM):

businessHoursStart: 20,
businessHoursEnd: 5,
is24HourOperation: false

24-hour operation:

is24HourOperation: true

Dropdown Data Formats

Brand

{ "uuid": "b1a2c3d4-...", "name": "RUSH Salon" }

Branch

{
  "uuid": "br1-0001-...",
  "brand_uuid": "b1a2c3d4-...",
  "name": "Downtown"
}

Service

{
  "uuid": "sv-0001",
  "name": "Haircut",
  "duration": 60,
  "brands": ["b1a2c3d4-..."],
  "branches": ["br1-0001-...", "br1-0002-..."],
  "specialists": ["sp-0001", "sp-0002"]
}
  • duration — service duration in minutes, used to auto-compute booking end time
  • brands — list of brand UUIDs that offer this service
  • branches — list of branch UUIDs where this service is available
  • specialists — list of specialist UUIDs who can perform this service

Specialist

{
  "uuid": "sp-0001",
  "name": "Maria Santos",
  "services": ["sv-0001", "sv-0002"],
  "brand_uuid": "b1a2c3d4-...",
  "branch_uuid": "br1-0001-...",
  "availability": {
    "monday": ["09:00", "09:30", "10:00", "10:30", "11:00"],
    "tuesday": ["09:00", "09:30", "10:00"],
    "wednesday": [],
    "thursday": ["14:00", "14:30", "15:00"],
    "friday": ["09:00", "09:30", "10:00", "10:30"],
    "saturday": [],
    "sunday": []
  }
}
  • brand_uuid — single brand the specialist belongs to
  • branch_uuid — single branch the specialist is assigned to
  • availability — time slots per day of week (used by the availability side panel)
  • When a specialist + date are selected, the availability panel shows available/booked slots

Customer

{
  "uuid": "cu-0001",
  "name": "John Doe",
  "email": "john.doe@email.com",
  "phone": "+1 (555) 100-2001",
  "gender": "Male",
  "address": "123 Main St, New York, NY 10001",
  "notes": "Prefers short fade on sides"
}

All customer details are displayed in the view booking modal. New customers can be created inline from the booking form.

Customer Search & Selection

The booking form provides a searchable customer field:

  • Search by name or mobile — type in the search box to filter customers in real-time
  • Phone search — strips non-numeric characters for flexible matching (e.g. "555" matches "+1 (555) 100-2001")
  • Customer details — when a customer is selected, their full details (name, phone, email, address, notes) are displayed in a read-only form below
  • New customer creation — toggle to inline form with first name, last name, mobile, email
  • Duplicate check — "Check Record" button searches existing customers by phone/email/name before creating a new one. If matches found, user can select an existing customer instead

Time Slots

["09:00", "09:30", "10:00", "10:30", "11:00", "11:30", "12:00", ...]

Time slots outside business hours are automatically disabled in the booking form.

Drag & Drop

Events can be dragged and dropped between time slots in week and day views:

  • 15-minute snapping — drop position within the hour cell determines the minute offset (top quarter = :00, second = :15, third = :30, bottom = :45)
  • Auto-collapse — clicking any event or empty cell automatically collapses all expanded "+N more" sections
  • Duration preserved — the event's original duration is maintained after moving

Event Data Format

{
  "id": "s1",
  "title": "Haircut",
  "brand": "b1a2c3d4-...",
  "branch": "br1-0001-...",
  "service": "sv-0001",
  "specialist": "sp-0001",
  "customer": "cu-0001",
  "start": "2025-01-10T09:00:00.000Z",
  "end": "2025-01-10T10:00:00.000Z",
  "color": "#FF6B00"
}

Callbacks

All callbacks can be toggled on/off and have customizable function bodies:

Callback Fires When Payload
onEventCreate New booking saved { id, title, start, end }
onEventUpdate Booking edited { id, title, start, end }
onEventDelete Booking deleted { id }
onEventClick Booking clicked/viewed { id, title }
onDateChange Calendar navigated { date, direction }
onViewChange View switched { view }
onSlotClick Empty slot clicked { date, hour }
onEventDrop Event drag & dropped { id, from, to }
onSpecialistScheduleCheck Availability panel loads { specialist_uuid, specialist_name, date, day, available_slots, booked_slots, total_available, total_booked }
onBookingCancel Cancel booking confirmed { id, title, customer, specialist }
onBookingNoShow No-show confirmed { id, title, customer, specialist }
onBookingComplete Booking marked as complete { id, title, customer, specialist }

Custom Callback Functions

Each callback has an editable function body that receives a payload argument:

callbackFns: {
  onEventCreate: 'console.log("Event created:", payload)',
  onSpecialistScheduleCheck: `
    // Fetch real availability from API
    fetch('/api/availability?specialist=' + payload.specialist_uuid + '&date=' + payload.date)
      .then(r => r.json())
      .then(data => console.log(data))
  `
}

Callback Examples

onEventCreate — Send new booking to API
callbackFns: {
  onEventCreate: `
    fetch('/api/bookings', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload)
    })
    .then(r => r.json())
    .then(data => console.log('Booking saved:', data.id))
    .catch(err => console.error('Failed to save:', err))
  `
}
onEventUpdate — Sync changes to backend
callbackFns: {
  onEventUpdate: `
    fetch('/api/bookings/' + payload.id, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload)
    })
    .then(() => console.log('Booking updated:', payload.id))
  `
}
onEventDelete — Delete from backend with confirmation log
callbackFns: {
  onEventDelete: `
    fetch('/api/bookings/' + payload.id, { method: 'DELETE' })
      .then(() => console.log('Booking deleted:', payload.id))
  `
}
onEventClick — Track booking views in analytics
callbackFns: {
  onEventClick: `
    if (window.gtag) {
      gtag('event', 'view_booking', {
        booking_id: payload.id,
        booking_title: payload.title
      })
    }
    console.log('Viewed booking:', payload.title)
  `
}
onDateChange — Load events for the new date range
callbackFns: {
  onDateChange: `
    console.log('Calendar navigated:', payload.direction, 'to', payload.date)
    // Fetch events for the new visible range
    // fetch('/api/bookings?date=' + payload.date)
    //   .then(r => r.json())
    //   .then(events => store.commit('SET_EVENTS', events))
  `
}
onViewChange — Persist user's preferred view
callbackFns: {
  onViewChange: `
    localStorage.setItem('calendarView', payload.view)
    console.log('View changed to:', payload.view)
  `
}
onSlotClick — Pre-fill booking from clicked slot
callbackFns: {
  onSlotClick: `
    console.log('Slot clicked:', payload.date, 'at', payload.hour + ':00')
  `
}
onEventDrop — Update booking after drag & drop
callbackFns: {
  onEventDrop: `
    fetch('/api/bookings/' + payload.id + '/reschedule', {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ from: payload.from, to: payload.to })
    })
    .then(() => console.log('Booking rescheduled:', payload.id))
  `
}
onSpecialistScheduleCheck — Fetch real-time availability from API
callbackFns: {
  onSpecialistScheduleCheck: `
    console.log('Checking schedule for', payload.specialist_name, 'on', payload.date)
    console.log('Day:', payload.day)
    console.log('Available slots:', payload.total_available)
    console.log('Booked slots:', payload.total_booked)
    console.log('Available times:', payload.available_slots.join(', '))
    console.log('Booked times:', payload.booked_slots.join(', '))

    // Example: Fetch real availability from your API
    // fetch('/api/specialists/' + payload.specialist_uuid + '/availability?date=' + payload.date)
    //   .then(r => r.json())
    //   .then(data => {
    //     // Update specialist availability dynamically
    //     console.log('Real availability:', data)
    //   })
  `
}
onBookingCancel — Handle booking cancellation
callbackFns: {
  onBookingCancel: `
    fetch('/api/bookings/' + payload.id + '/cancel', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ reason: 'customer_request' })
    })
    .then(() => showSuccess('Booking cancelled successfully'))
    .catch(err => showError('Failed to cancel: ' + err.message))
  `
}
onBookingNoShow — Record no-show and notify
callbackFns: {
  onBookingNoShow: `
    fetch('/api/bookings/' + payload.id + '/no-show', { method: 'POST' })
      .then(() => showSuccess('No-show recorded for ' + payload.title))
      .catch(err => showError('Failed to record no-show: ' + err.message))
  `
}
onBookingComplete — Mark service as delivered
callbackFns: {
  onBookingComplete: `
    fetch('/api/bookings/' + payload.id + '/complete', { method: 'POST' })
      .then(() => showSuccess('Service completed: ' + payload.title))
      .catch(err => showError('Failed to complete: ' + err.message))
  `
}

Note: All callback functions receive showSuccess(message) and showError(message) as arguments. Call these to display success (green) or error (red) toast notifications in the booking dialog. Toasts auto-dismiss after 4 seconds or can be clicked to dismiss.

Disabling Specific Callbacks

Toggle individual callbacks on/off:

// Via config
callbacks: {
  onEventCreate: true,     // enabled
  onEventUpdate: true,     // enabled
  onEventDelete: false,    // disabled
  onEventClick: false,     // disabled
  onDateChange: true,      // enabled
  onViewChange: false,     // disabled
  onSlotClick: false,      // disabled
  onEventDrop: true,       // enabled
  onSpecialistScheduleCheck: true,  // enabled
  onBookingCancel: true,   // enabled
  onBookingNoShow: true,   // enabled
  onBookingComplete: true  // enabled
}

// Or at runtime
store.commit('SET_CONFIG', { key: 'callbacks.onEventDelete', value: false })
store.commit('SET_CONFIG', { key: 'callbacks.onEventDrop', value: true })
store.commit('SET_CONFIG', { key: 'callbacks.onBookingCancel', value: false })
store.commit('SET_CONFIG', { key: 'callbacks.onBookingComplete', value: true })

Updating Callback Functions at Runtime

// Update a callback function body
store.commit('SET_CONFIG', {
  key: 'callbackFns.onEventCreate',
  value: 'fetch("/api/bookings", { method: "POST", body: JSON.stringify(payload) })'
})

// Reset to default
store.commit('SET_CONFIG', {
  key: 'callbackFns.onEventCreate',
  value: 'console.log("Event created:", payload)'
})

Vuex Store API

Mutations

Mutation Payload Description
SET_DATE Date Set current calendar date
SET_VIEW string Set view ('month', 'week', 'day')
SET_EVENTS array Replace all events
ADD_EVENT object Add a single event
UPDATE_EVENT object Update event by id
DELETE_EVENT string Delete event by id
SET_CONFIG { key, value } Update config (supports dot notation: 'bookingFields.brand')
SHOW_EVENT_MODAL boolean Show/hide create/edit modal
SHOW_VIEW_MODAL boolean Show/hide view modal

Actions

Action Payload Description
next — Navigate to next period
prev — Navigate to previous period
today — Navigate to today
changeView string Switch view with callback
addEvent object Add event with callback
updateEvent object Update event with callback
deleteEvent string Delete event with callback
openCreateModal slot? Open create booking modal
openEditModal event Open edit booking modal
openViewModal event Open view booking modal
moveEvent { id, newDate, newHour, newMinute } Move event (drag & drop, snaps to 15-min intervals)
clearAllEvents — Remove all events
resetEvents — Reset to sample data

Programmatic Config Updates

Update any config at runtime:

// Simple value
store.commit('SET_CONFIG', { key: 'timeFormat', value: '24h' })

// Nested value
store.commit('SET_CONFIG', { key: 'bookingFields.color', value: false })

// Replace dropdown options
store.commit('SET_CONFIG', {
  key: 'dropdownOptions.brand',
  value: [
    { uuid: 'b1', name: 'My Brand' },
    { uuid: 'b2', name: 'Other Brand' }
  ]
})

// Replace all events
store.commit('SET_EVENTS', myEventsArray)

Custom Create/Edit Modal (Slots)

Replace the built-in booking modal with your own component using the create-modal slot:

<BookingCalendar>
  <template #create-modal="{ form, event, close, isEdit }">
    <MyCustomBookingDialog
      v-if="$store.state.showEventModal"
      :slot-data="form"
      :event="event"
      :is-edit="isEdit"
      @close="close"
      @save="handleSave"
    />
  </template>
</BookingCalendar>

Slot Props

Prop Type Description
form object|null The selected slot data ({ date, hour }) when creating
event object|null The selected event object when editing
close function Call to close the modal
isEdit boolean true if editing, false if creating

Available Internal Slots

The built-in EventModal also exposes these slots for partial customization:

Slot Props Description
header { form, isEdit, close } Replace the hero header
content { form, isEdit } Replace the entire form body (all cards)
before-fields { form, isEdit } Insert content before the form fields
after-fields { form, isEdit } Insert content after the form fields
footer { form, isEdit, save, close, deleteEvent } Replace the footer buttons
<!-- Example: Add a note above the form -->
<BookingCalendar>
  <template #create-modal="{ form, event, close, isEdit }">
    <EventModal v-if="$store.state.showEventModal">
      <template #before-fields>
        <div class="my-note">Please fill all required fields</div>
      </template>
      <template #footer="{ save, close }">
        <button @click="close">Cancel</button>
        <button @click="save">Book Now</button>
      </template>
    </EventModal>
  </template>
</BookingCalendar>

License

MIT

Related Packages

vuejs-countdown-timer

Vue 2 countdown and timer component

53,691 48
@reservi/vue

Wrapper de Vue 3 para Reservi Calendar.

2,328
ner-text-annotation

A vue2component for NER text annotation

458
qalendar

Event calendar and date picker for Vue 3

156,674 293

Version History

Version Released Vue License
2.8.0 ^2.7.0 MIT
2.7.2 ^2.7.0 MIT
2.7.1 ^2.7.0 MIT
2.7.0 ^2.7.0 MIT
2.6.3 ^2.7.0 MIT