---
title: "Vue Timeline Drag People onto Events Example | Mobiscroll"
component: "Timeline"
framework: "Vue"
library_version: "6.2.0"
url: "https://demo.mobiscroll.com/vue/timeline/drop-assignee-on-event-from-list"
---

# Drag People onto Events - Vue Timeline demo

Build a team assignment interface on the Vue Timeline — drag people from an external list onto meetings with conflict validation. For Vue and Nuxt.

- [Live demo](https://demo.mobiscroll.com/vue/timeline/drop-assignee-on-event-from-list)
- [View SFC JS code on GitHub](https://github.com/acidb/mobiscroll-demos-vue/tree/main/src/demos/eventcalendar/timeline/drop-assignee-on-event-from-list)
- [View SFC TS code on GitHub](https://github.com/acidb/mobiscroll-demos-vue-ts/tree/main/src/demos/eventcalendar/timeline/drop-assignee-on-event-from-list)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/timeline/drop-assignee-on-event-from-list.md), [jQuery](https://demo.mobiscroll.com/jquery/timeline/drop-assignee-on-event-from-list.md), [Angular](https://demo.mobiscroll.com/angular/timeline/drop-assignee-on-event-from-list.md), [React](https://demo.mobiscroll.com/react/timeline/drop-assignee-on-event-from-list.md)

## Description

Assigning people to meetings that are already booked across multiple rooms is a common scheduling challenge. Instead of dropping external items onto the calendar grid to create new events, the goal here is to drop them onto existing events, turning each meeting into an individual drop target where attendees can be assigned directly.

This is where the `MbscDropcontainer` comes in. Each event rendered through the [`timelineEvent`](https://mobiscroll.com/docs/vue/eventcalendar/api#slot-timelineEvent) function is initialized as a drop container, turning every meeting card into a valid drop zone. The external list of team members is set up using the draggable component, so each person can be grabbed and dropped onto any event on the timeline. The [`item-drop`](https://mobiscroll.com/docs/vue/eventcalendar/drag-and-drop#event-onItemDrop) event handles the assignment, while [`item-drag-enter`](https://mobiscroll.com/docs/vue/eventcalendar/drag-and-drop#event-onItemDragEnter) and [`item-drag-leave`](https://mobiscroll.com/docs/vue/eventcalendar/drag-and-drop#event-onItemDragLeave) provide real-time visual feedback.

Beyond simple assignment, the demo includes practical validation. Before accepting a drop, it checks whether the person is already assigned to the target meeting and whether they have a time conflict with another meeting in the same slot. Assignments can also be removed by clicking on an attendee chip, with an undo option.

**Looking for dropping external items onto the calendar grid instead?** [Check out the external drag & drop example →](https://demo.mobiscroll.com/vue/timeline/external-drag-drop-sortable-dragula)

## What this demo shows

- A weekly timeline view shows multiple resources in horizontal lanes, with meetings displayed directly on the timeline for each resource.
- **Timeline header** The top area includes the timeline date header, an hourly scale beneath it. The default navigation controls are above that with a month picker, previous and next arrows, and a Today button.
- **Resource timeline** The main timeline area scrolls horizontally and shows timed events positioned within their matching resource rows.
- **Event cards** Each event uses a gray card with a colored vertical stripe and attendee chips that show the initials of assigned people.
- **Team member list** A separate list of people appears beside the timeline, with each item showing initials, name, and a secondary line that indicates either no assignment or the number of meetings they are currently assigned to.
- **Drag feedback** Team members can be dragged from the list onto existing meetings. While dragging, events show dashed outlines and a placeholder message that says `Drop people to assign`, and a drag preview follows the pointer.
- **Drop validation states** Events highlight in green when a drop is allowed and in red when a drop is blocked.
- **Assignment validation** Dropping is rejected if the same person is already assigned to that event or is assigned to another event that overlaps in time, and an error message appears at the bottom.
- **Successful assignment** A valid drop shows a confirmation message and adds that person's initials as a new attendee chip inside the target event.
- **Removing assignees** Hovering an attendee chip reveals an `X` action that removes that person from the event.
- **Undo flow** Removing an assignee opens a snackbar with a removal message and an Undo action. If Undo is used, the assignment is restored and a confirmation toast appears.

## Best for

- **Team assignment on scheduled work** Assigning people to meetings, sessions, or other already-scheduled timeline events without creating new events.
- **Resource-based planning** Managing assignments across parallel resource lanes such as rooms, workstations, or service areas.
- **Direct manipulation workflows** Interfaces where users need to drag people, equipment, or other external resources onto existing scheduled items.
- **Conflict-aware scheduling** Planning scenarios where duplicate assignments and overlapping time conflicts must be prevented during drop actions.

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-themeVariant) - option
- [`view`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-view) - option
- [`data`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-data) - option
- [`resources`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-resources) - option
- [`dragToCreate`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-dragToCreate) - option
- [`dragToMove`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-dragToMove) - option
- [`dragToResize`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-dragToResize) - option
- [`clickToCreate`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-clickToCreate) - option
- [`eventDelete`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-eventDelete) - option
- [`showEventTooltip`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-showEventTooltip) - option
- [`close`](https://mobiscroll.com/docs/vue/notifications/toast#event-onClose) - event
- [`close`](https://mobiscroll.com/docs/vue/notifications/snackbar#event-onClose) - event

## Code

### SFC JS

```vue
<script setup>
import {
  MbscDraggable,
  MbscDropcontainer,
  MbscEventcalendar,
  MbscPage,
  MbscSnackbar,
  MbscToast,
  setOptions
} from '@mobiscroll/vue'
import { reactive, ref } from 'vue'

setOptions({
  theme: 'ios',
  themeVariant: 'light'
})

const employees = [
  { id: 'emp1', name: 'Alice Martin', avatar: 'AM', color: '#e74c3c' },
  { id: 'emp2', name: 'Bob Johnson', avatar: 'BJ', color: '#3498db' },
  { id: 'emp3', name: 'Carol Smith', avatar: 'CS', color: '#2ecc71' },
  { id: 'emp4', name: 'David Lee', avatar: 'DL', color: '#f39c12' },
  { id: 'emp5', name: 'Eva Chen', avatar: 'EC', color: '#9b59b6' },
  { id: 'emp6', name: 'Frank Diaz', avatar: 'FD', color: '#1abc9c' },
  { id: 'emp7', name: 'Grace Kim', avatar: 'GK', color: '#e67e22' },
  { id: 'emp8', name: 'Henry Patel', avatar: 'HP', color: '#34495e' },
  { id: 'emp9', name: 'Ivy Torres', avatar: 'IT', color: '#e84393' },
  { id: 'emp10', name: 'Jack Murphy', avatar: 'JM', color: '#0984e3' }
]

const rooms = [
  { id: 1, name: 'Conference Room' },
  { id: 2, name: 'Board Room' },
  { id: 3, name: 'Meeting Room' },
  { id: 4, name: 'Training Room' }
]

const meetings = ref([
  {
    id: 'evt1',
    title: 'Sprint Planning',
    start: '2026-11-08T09:00',
    end: '2026-11-08T11:00',
    resource: 1,
    color: '#b52db9',
    attendees: [
      { id: 'emp1', name: 'Alice Martin', avatar: 'AM', color: '#e74c3c' },
      { id: 'emp2', name: 'Bob Johnson', avatar: 'BJ', color: '#3498db' }
    ]
  },
  {
    id: 'evt2',
    title: 'Budget Review',
    start: '2026-11-08T10:00',
    end: '2026-11-08T13:00',
    resource: 2,
    color: '#669ce2',
    attendees: []
  },
  {
    id: 'evt3',
    title: 'Client Presentation',
    start: '2026-11-08T15:00',
    end: '2026-11-08T18:00',
    resource: 2,
    color: '#88bd42',
    attendees: [
      { id: 'emp3', name: 'Carol Smith', avatar: 'CS', color: '#2ecc71' },
      { id: 'emp4', name: 'David Lee', avatar: 'DL', color: '#f39c12' }
    ]
  },
  {
    id: 'evt4',
    title: 'Project Kickoff',
    start: '2026-11-08T09:00',
    end: '2026-11-08T11:00',
    resource: 3,
    color: '#b6962f',
    attendees: []
  },
  {
    id: 'evt5',
    title: 'New Hire Orientation',
    start: '2026-11-08T13:00',
    end: '2026-11-08T16:00',
    resource: 4,
    color: '#c864f0',
    attendees: []
  },
  {
    id: 'evt6',
    title: 'Design Review',
    start: '2026-11-08T13:00',
    end: '2026-11-08T15:00',
    resource: 1,
    color: '#c7682d',
    attendees: []
  },
  {
    id: 'evt7',
    title: 'Product Demo',
    start: '2026-11-09T09:00',
    end: '2026-11-09T11:00',
    resource: 1,
    color: '#ad2b6c',
    attendees: []
  },
  {
    id: 'evt8',
    title: 'Stakeholder Update',
    start: '2026-11-09T10:00',
    end: '2026-11-09T12:00',
    resource: 2,
    color: '#0f60ca',
    attendees: []
  },
  {
    id: 'evt9',
    title: 'Code Review',
    start: '2026-11-09T10:00',
    end: '2026-11-09T12:00',
    resource: 3,
    color: '#56a823',
    attendees: []
  },
  {
    id: 'evt10',
    title: 'Safety Training',
    start: '2026-11-09T13:00',
    end: '2026-11-09T16:00',
    resource: 4,
    color: '#aa36d8',
    attendees: []
  },
  {
    id: 'evt11',
    title: 'Retrospective',
    start: '2026-11-10T09:00',
    end: '2026-11-10T11:00',
    resource: 1,
    color: '#c45f20',
    attendees: []
  },
  {
    id: 'evt12',
    title: 'Board Briefing',
    start: '2026-11-10T13:00',
    end: '2026-11-10T16:00',
    resource: 2,
    color: '#1e58a5',
    attendees: []
  },
  {
    id: 'evt13',
    title: 'Marketing Sync',
    start: '2026-11-10T10:00',
    end: '2026-11-10T12:00',
    resource: 3,
    color: '#549e27',
    attendees: []
  },
  {
    id: 'evt14',
    title: 'API Workshop',
    start: '2026-11-10T13:00',
    end: '2026-11-10T16:00',
    resource: 4,
    color: '#7c1ca1',
    attendees: []
  },
  {
    id: 'evt15',
    title: 'Architecture Review',
    start: '2026-11-11T09:00',
    end: '2026-11-11T11:30',
    resource: 1,
    color: '#a7511c',
    attendees: []
  },
  {
    id: 'evt16',
    title: 'Quarterly Planning',
    start: '2026-11-11T13:00',
    end: '2026-11-11T16:00',
    resource: 2,
    color: '#13488d',
    attendees: []
  },
  {
    id: 'evt17',
    title: 'Hiring Debrief',
    start: '2026-11-11T10:00',
    end: '2026-11-11T12:00',
    resource: 3,
    color: '#51ac19',
    attendees: []
  },
  {
    id: 'evt18',
    title: 'Team Standup',
    start: '2026-11-07T09:00',
    end: '2026-11-07T11:00',
    resource: 1,
    color: '#e74c3c',
    attendees: []
  },
  {
    id: 'evt19',
    title: 'Daily Scrum',
    start: '2026-11-07T10:00',
    end: '2026-11-07T13:00',
    resource: 2,
    color: '#3498db',
    attendees: []
  },
  {
    id: 'evt20',
    title: 'Lunch Meeting',
    start: '2026-11-07T12:00',
    end: '2026-11-07T14:00',
    resource: 3,
    color: '#2ecc71',
    attendees: []
  },
  {
    id: 'evt21',
    title: 'Weekly Sync',
    start: '2026-11-07T14:00',
    end: '2026-11-07T16:00',
    resource: 4,
    color: '#f39c12',
    attendees: []
  },
  {
    id: 'evt22',
    title: 'Morning Briefing',
    start: '2026-11-06T09:00',
    end: '2026-11-06T11:00',
    resource: 1,
    color: '#9b59b6',
    attendees: []
  },
  {
    id: 'evt23',
    title: 'Status Update',
    start: '2026-11-06T11:00',
    end: '2026-11-06T14:00',
    resource: 2,
    color: '#1abc9c',
    attendees: []
  },
  {
    id: 'evt24',
    title: 'Client Call',
    start: '2026-11-06T13:00',
    end: '2026-11-06T15:00',
    resource: 3,
    color: '#e67e22',
    attendees: []
  },
  {
    id: 'evt25',
    title: 'End of Day Review',
    start: '2026-11-06T16:00',
    end: '2026-11-06T18:00',
    resource: 4,
    color: '#34495e',
    attendees: []
  }
])

const myView = {
  timeline: {
    type: 'week',
    startDay: 1,
    endDay: 5,
    startTime: '08:00',
    endTime: '18:00',
    timeCellStep: 60,
    timeLabelStep: 60,
    virtualScroll: false
  }
}

const dragElements = ref([])
const dropZoneEls = reactive({})
const dropStates = reactive({})
const isExternalDragging = ref(false)

const toastMessage = ref('')
const toastColor = ref('')
const isToastOpen = ref(false)

const snackbarMessage = ref('')
const snackbarButton = ref(null)
const isSnackbarOpen = ref(false)

function getAssignmentCount(empId) {
  return meetings.value.filter((m) => m.attendees.some((a) => a.id === empId)).length
}

function findConflict(empId, targetEventId) {
  const target = meetings.value.find((m) => m.id === targetEventId)
  if (!target) return null

  const targetStart = new Date(target.start).getTime()
  const targetEnd = new Date(target.end).getTime()

  for (const m of meetings.value) {
    if (m.id === targetEventId) continue
    if (!m.attendees.some((a) => a.id === empId)) continue
    const mStart = new Date(m.start).getTime()
    const mEnd = new Date(m.end).getTime()
    if (mStart < targetEnd && mEnd > targetStart) return m
  }
  return null
}

function setDropZoneEl(el, eventId) {
  dropZoneEls[eventId] = el
}

function showToast(message, color) {
  toastMessage.value = message
  toastColor.value = color
  isToastOpen.value = true
}

function onDragStart() {
  const onMove = () => {
    isExternalDragging.value = true
    document.removeEventListener('pointermove', onMove)
  }
  const onUp = () => {
    isExternalDragging.value = false
    document.removeEventListener('pointermove', onMove)
    document.removeEventListener('pointerup', onUp)
  }
  document.addEventListener('pointermove', onMove)
  document.addEventListener('pointerup', onUp)
}

function onItemDrop(dropEvent, eventId) {
  const employee = dropEvent.data
  dropStates[eventId] = ''

  const meeting = meetings.value.find((m) => m.id === eventId)
  if (!meeting) return

  if (meeting.attendees.some((a) => a.id === employee.id)) {
    showToast(employee.name + ' is already assigned', 'danger')
    return
  }

  const conflict = findConflict(employee.id, eventId)
  if (conflict) {
    showToast(employee.name + ' already has a ' + conflict.title + ' on this timeslot', 'danger')
    return
  }

  meetings.value = meetings.value.map((m) =>
    m.id === eventId
      ? {
          ...m,
          attendees: [
            ...m.attendees,
            { id: employee.id, name: employee.name, avatar: employee.avatar, color: employee.color }
          ]
        }
      : m
  )

  showToast(employee.name + ' assigned to ' + meeting.title, 'success')
}

function onItemDragEnter(enterEvent, eventId) {
  const employee = enterEvent.data
  const meeting = meetings.value.find((m) => m.id === eventId)
  if (!meeting) return

  if (employee) {
    if (meeting.attendees.some((a) => a.id === employee.id)) {
      dropStates[eventId] = 'mds-drop-conflict'
    } else {
      const conflict = findConflict(employee.id, eventId)
      dropStates[eventId] = conflict ? 'mds-drop-conflict' : 'mds-drop-active'
    }
  } else {
    dropStates[eventId] = 'mds-drop-active'
  }
}

function onItemDragLeave(eventId) {
  dropStates[eventId] = ''
}

function removeAttendee(eventId, empId, domEvent) {
  domEvent.stopPropagation()

  const meeting = meetings.value.find((m) => m.id === eventId)
  if (!meeting) return

  const idx = meeting.attendees.findIndex((a) => a.id === empId)
  if (idx === -1) return

  const removed = meeting.attendees[idx]

  meetings.value = meetings.value.map((m) =>
    m.id === eventId ? { ...m, attendees: m.attendees.filter((a) => a.id !== empId) } : m
  )

  snackbarMessage.value = removed.name + ' removed from ' + meeting.title
  snackbarButton.value = {
    text: 'Undo',
    action: () => {
      meetings.value = meetings.value.map((m) => {
        if (m.id !== eventId) return m
        const attendees = [...m.attendees]
        attendees.splice(idx, 0, removed)
        return { ...m, attendees }
      })
      showToast('Assignment restored', 'success')
    }
  }
  isSnackbarOpen.value = true
}
</script>

<template>
  <MbscPage
    :cssClass="`mds-drop-on-events${
      isExternalDragging ? ' mds-drop-on-events-external-dragging' : ''
    }`"
  >
    <div class="mbsc-grid mbsc-no-padding">
      <div class="mbsc-row">
        <div class="mbsc-col-sm-3 mbsc-flex-col mds-drop-on-events-sidebar">
          <div class="mbsc-form-group-title">Team Members</div>
          <div class="mds-drop-on-events-employee-list mbsc-flex">
            <template v-for="(emp, i) in employees" :key="emp.id">
              <div
                ref="dragElements"
                class="mds-drop-on-events-employee-item mbsc-flex"
                @pointerdown="onDragStart"
              >
                <div
                  class="mds-drop-on-events-employee-avatar mbsc-flex"
                  :style="{ background: emp.color }"
                >
                  {{ emp.avatar }}
                </div>
                <div class="mds-drop-on-events-employee-info mbsc-flex">
                  <div class="mds-drop-on-events-employee-name">{{ emp.name }}</div>
                  <div class="mds-drop-on-events-employee-count">
                    <template v-if="getAssignmentCount(emp.id) > 0">
                      {{ getAssignmentCount(emp.id) }} meeting{{
                        getAssignmentCount(emp.id) > 1 ? 's' : ''
                      }}
                    </template>
                    <template v-else>No assignments</template>
                  </div>
                </div>
                <MbscDraggable :element="dragElements[i]" :dragData="emp" />
              </div>
            </template>
          </div>
        </div>
        <div class="mbsc-col-sm-9 mds-drop-on-events-calendar-wrapper">
          <MbscEventcalendar
            :view="myView"
            :data="meetings"
            :resources="rooms"
            :dragToCreate="false"
            :dragToMove="false"
            :dragToResize="false"
            :clickToCreate="false"
            :eventDelete="false"
            :showEventTooltip="false"
          >
            <template #timelineEvent="data">
              <div
                :ref="(el) => setDropZoneEl(el, data.original.id)"
                class="mds-drop-on-events-custom-event mbsc-flex"
                :class="dropStates[data.original.id]"
                :style="{ borderLeft: '4px solid ' + data.original.color }"
              >
                <MbscDropcontainer
                  :element="dropZoneEls[data.original.id]"
                  @item-drop="onItemDrop($event, data.original.id)"
                  @item-drag-enter="onItemDragEnter($event, data.original.id)"
                  @item-drag-leave="onItemDragLeave(data.original.id)"
                >
                  <div class="mds-drop-on-events-event-header mbsc-flex">
                    <div class="mds-drop-on-events-event-title">{{ data.original.title }}</div>
                    <div class="mds-drop-on-events-event-time">
                      {{ data.start }} - {{ data.end }}
                    </div>
                  </div>
                  <div
                    v-if="data.original.attendees && data.original.attendees.length"
                    class="mds-drop-on-events-event-attendees mbsc-flex"
                  >
                    <span
                      v-for="attendee in data.original.attendees"
                      :key="attendee.id"
                      class="mds-drop-on-events-attendee-chip"
                      :style="{ background: attendee.color }"
                      :title="attendee.name + ' (click to remove)'"
                      @click="removeAttendee(data.original.id, attendee.id, $event)"
                    >
                      {{ attendee.avatar }}
                      <span class="mds-drop-on-events-attendee-remove">×</span>
                    </span>
                  </div>
                  <div class="mds-drop-on-events-event-drop-hint">Drop people to assign</div>
                </MbscDropcontainer>
              </div>
            </template>
          </MbscEventcalendar>
        </div>
      </div>
    </div>
    <MbscToast
      :message="toastMessage"
      :color="toastColor"
      :isOpen="isToastOpen"
      @close="isToastOpen = false"
    />
    <MbscSnackbar
      :message="snackbarMessage"
      :button="snackbarButton"
      :isOpen="isSnackbarOpen"
      @close="isSnackbarOpen = false"
    />
  </MbscPage>
</template>

<style>
.mds-drop-on-events,
.mds-drop-on-events .mbsc-grid,
.mds-drop-on-events .mbsc-row {
  height: 100%;
}
.mds-drop-on-events-sidebar {
  overflow-y: auto;
}
.mds-drop-on-events-employee-list {
  padding: 8px;
  flex-direction: column;
  gap: 4px;
}
.mds-drop-on-events-employee-item {
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: 10px;
  cursor: grab;
  user-select: none;
  background: rgba(128, 128, 128, 0.5);
  margin: 0 5px 5px 5px;
  touch-action: none;
  transition:
    background 0.15s,
    box-shadow 0.2s,
    transform 0.15s;
}
.mds-drop-on-events-employee-item:hover {
  background: rgba(128, 128, 128, 0.4);
  transform: translateY(-1px);
}
.mds-drop-on-events-employee-item:active {
  transform: translateY(0);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
.mds-drop-on-events-employee-avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  flex-shrink: 0;
  letter-spacing: 0.5px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}
.mds-drop-on-events-employee-info {
  flex-direction: column;
  overflow: hidden;
}
.mds-drop-on-events-employee-name {
  font-size: 15px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mds-drop-on-events-employee-count {
  font-size: 13px;
  opacity: 0.55;
}
.mds-drop-on-events-calendar-wrapper {
  border-left: 1px solid rgba(0, 0, 0, 0.1);
}
/* Drag clone is appended to body, outside the root — keep unscoped */
.mds-drop-on-events-employee-item.mbsc-drag-clone {
  opacity: 0.8;
}
.mds-drop-on-events-custom-event {
  background: #cccccc;
  border-radius: 6px;
  padding: 6px 8px;
  height: 100%;
  box-sizing: border-box;
  flex-direction: column;
  gap: 4px;
  overflow: hidden;
  outline: 2px solid transparent;
  transition: background 0.15s;
  position: relative;
}
.mds-drop-on-events-event-header {
  flex-direction: column;
  gap: 1px;
}
.mds-drop-on-events-event-title {
  font-size: 14px;
  font-weight: 600;
  color: #181818;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mds-drop-on-events-event-time {
  font-size: 12px;
  color: #545454;
}
.mds-drop-on-events-event-attendees {
  flex-wrap: wrap;
  gap: 3px;
}
/* Drop hint - hidden by default, shown only during external drag */
.mds-drop-on-events-event-drop-hint {
  display: none;
  font-size: 11px;
  font-style: italic;
  color: #686868;
}
/* Show drop hints and dashed borders on events during external drag */
.mds-drop-on-events-external-dragging .mds-drop-on-events-event-drop-hint {
  display: block;
}
.mds-drop-on-events-external-dragging .mds-drop-on-events-custom-event {
  outline: 2px dashed #b9b9b9;
}
.mds-drop-on-events-attendee-chip {
  display: flex;
  width: 25px;
  height: 25px;
  border-radius: 50%;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 9px;
  font-weight: 700;
  border: 2px solid rgba(255, 255, 255, 0.5);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
  cursor: pointer;
  position: relative;
}
.mds-drop-on-events-attendee-remove {
  display: none;
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  border-radius: 50%;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  line-height: 1;
}
.mds-drop-on-events-attendee-chip:hover .mds-drop-on-events-attendee-remove {
  display: flex;
}
.mds-drop-on-events-custom-event.mds-drop-active {
  cursor: copy;
  outline: 2px solid rgba(54, 133, 43, 0.8);
  background: rgba(180, 223, 173, 0.8);
}
.mds-drop-on-events-custom-event.mds-drop-conflict {
  cursor: not-allowed;
  outline: 2px solid rgba(145, 34, 34, 0.8);
  background: rgba(235, 194, 194, 0.8);
}
.mds-drop-on-events .mbsc-timeline-event {
  min-height: 80px;
}
.mds-drop-on-events .mbsc-schedule-event-inner {
  height: 100%;
}
</style>
```

### SFC TS

```vue
<script setup lang="ts">
import {
  MbscDraggable,
  MbscDropcontainer,
  MbscEventcalendar,
  MbscPage,
  MbscSnackbar,
  MbscToast,
  setOptions
} from '@mobiscroll/vue'
import type {
  MbscCalendarEvent,
  MbscEventcalendarView,
  MbscItemDragEvent,
  MbscResource
} from '@mobiscroll/vue'
import { reactive, ref } from 'vue'

setOptions({
  theme: 'ios',
  themeVariant: 'light'
})

interface Employee {
  id: string
  name: string
  avatar: string
  color: string
}

interface Meeting extends MbscCalendarEvent {
  id: string
  title: string
  color: string
  attendees: Employee[]
}

const employees: Employee[] = [
  { id: 'emp1', name: 'Alice Martin', avatar: 'AM', color: '#e74c3c' },
  { id: 'emp2', name: 'Bob Johnson', avatar: 'BJ', color: '#3498db' },
  { id: 'emp3', name: 'Carol Smith', avatar: 'CS', color: '#2ecc71' },
  { id: 'emp4', name: 'David Lee', avatar: 'DL', color: '#f39c12' },
  { id: 'emp5', name: 'Eva Chen', avatar: 'EC', color: '#9b59b6' },
  { id: 'emp6', name: 'Frank Diaz', avatar: 'FD', color: '#1abc9c' },
  { id: 'emp7', name: 'Grace Kim', avatar: 'GK', color: '#e67e22' },
  { id: 'emp8', name: 'Henry Patel', avatar: 'HP', color: '#34495e' },
  { id: 'emp9', name: 'Ivy Torres', avatar: 'IT', color: '#e84393' },
  { id: 'emp10', name: 'Jack Murphy', avatar: 'JM', color: '#0984e3' }
]

const rooms: MbscResource[] = [
  { id: 1, name: 'Conference Room' },
  { id: 2, name: 'Board Room' },
  { id: 3, name: 'Meeting Room' },
  { id: 4, name: 'Training Room' }
]

const meetings = ref<Meeting[]>([
  {
    id: 'evt1',
    title: 'Sprint Planning',
    start: '2026-11-08T09:00',
    end: '2026-11-08T11:00',
    resource: 1,
    color: '#b52db9',
    attendees: [
      { id: 'emp1', name: 'Alice Martin', avatar: 'AM', color: '#e74c3c' },
      { id: 'emp2', name: 'Bob Johnson', avatar: 'BJ', color: '#3498db' }
    ]
  },
  {
    id: 'evt2',
    title: 'Budget Review',
    start: '2026-11-08T10:00',
    end: '2026-11-08T13:00',
    resource: 2,
    color: '#669ce2',
    attendees: []
  },
  {
    id: 'evt3',
    title: 'Client Presentation',
    start: '2026-11-08T15:00',
    end: '2026-11-08T18:00',
    resource: 2,
    color: '#88bd42',
    attendees: [
      { id: 'emp3', name: 'Carol Smith', avatar: 'CS', color: '#2ecc71' },
      { id: 'emp4', name: 'David Lee', avatar: 'DL', color: '#f39c12' }
    ]
  },
  {
    id: 'evt4',
    title: 'Project Kickoff',
    start: '2026-11-08T09:00',
    end: '2026-11-08T11:00',
    resource: 3,
    color: '#b6962f',
    attendees: []
  },
  {
    id: 'evt5',
    title: 'New Hire Orientation',
    start: '2026-11-08T13:00',
    end: '2026-11-08T16:00',
    resource: 4,
    color: '#c864f0',
    attendees: []
  },
  {
    id: 'evt6',
    title: 'Design Review',
    start: '2026-11-08T13:00',
    end: '2026-11-08T15:00',
    resource: 1,
    color: '#c7682d',
    attendees: []
  },
  {
    id: 'evt7',
    title: 'Product Demo',
    start: '2026-11-09T09:00',
    end: '2026-11-09T11:00',
    resource: 1,
    color: '#ad2b6c',
    attendees: []
  },
  {
    id: 'evt8',
    title: 'Stakeholder Update',
    start: '2026-11-09T10:00',
    end: '2026-11-09T12:00',
    resource: 2,
    color: '#0f60ca',
    attendees: []
  },
  {
    id: 'evt9',
    title: 'Code Review',
    start: '2026-11-09T10:00',
    end: '2026-11-09T12:00',
    resource: 3,
    color: '#56a823',
    attendees: []
  },
  {
    id: 'evt10',
    title: 'Safety Training',
    start: '2026-11-09T13:00',
    end: '2026-11-09T16:00',
    resource: 4,
    color: '#aa36d8',
    attendees: []
  },
  {
    id: 'evt11',
    title: 'Retrospective',
    start: '2026-11-10T09:00',
    end: '2026-11-10T11:00',
    resource: 1,
    color: '#c45f20',
    attendees: []
  },
  {
    id: 'evt12',
    title: 'Board Briefing',
    start: '2026-11-10T13:00',
    end: '2026-11-10T16:00',
    resource: 2,
    color: '#1e58a5',
    attendees: []
  },
  {
    id: 'evt13',
    title: 'Marketing Sync',
    start: '2026-11-10T10:00',
    end: '2026-11-10T12:00',
    resource: 3,
    color: '#549e27',
    attendees: []
  },
  {
    id: 'evt14',
    title: 'API Workshop',
    start: '2026-11-10T13:00',
    end: '2026-11-10T16:00',
    resource: 4,
    color: '#7c1ca1',
    attendees: []
  },
  {
    id: 'evt15',
    title: 'Architecture Review',
    start: '2026-11-11T09:00',
    end: '2026-11-11T11:30',
    resource: 1,
    color: '#a7511c',
    attendees: []
  },
  {
    id: 'evt16',
    title: 'Quarterly Planning',
    start: '2026-11-11T13:00',
    end: '2026-11-11T16:00',
    resource: 2,
    color: '#13488d',
    attendees: []
  },
  {
    id: 'evt17',
    title: 'Hiring Debrief',
    start: '2026-11-11T10:00',
    end: '2026-11-11T12:00',
    resource: 3,
    color: '#51ac19',
    attendees: []
  },
  {
    id: 'evt18',
    title: 'Team Standup',
    start: '2026-11-07T09:00',
    end: '2026-11-07T11:00',
    resource: 1,
    color: '#e74c3c',
    attendees: []
  },
  {
    id: 'evt19',
    title: 'Daily Scrum',
    start: '2026-11-07T10:00',
    end: '2026-11-07T13:00',
    resource: 2,
    color: '#3498db',
    attendees: []
  },
  {
    id: 'evt20',
    title: 'Lunch Meeting',
    start: '2026-11-07T12:00',
    end: '2026-11-07T14:00',
    resource: 3,
    color: '#2ecc71',
    attendees: []
  },
  {
    id: 'evt21',
    title: 'Weekly Sync',
    start: '2026-11-07T14:00',
    end: '2026-11-07T16:00',
    resource: 4,
    color: '#f39c12',
    attendees: []
  },
  {
    id: 'evt22',
    title: 'Morning Briefing',
    start: '2026-11-06T09:00',
    end: '2026-11-06T11:00',
    resource: 1,
    color: '#9b59b6',
    attendees: []
  },
  {
    id: 'evt23',
    title: 'Status Update',
    start: '2026-11-06T11:00',
    end: '2026-11-06T14:00',
    resource: 2,
    color: '#1abc9c',
    attendees: []
  },
  {
    id: 'evt24',
    title: 'Client Call',
    start: '2026-11-06T13:00',
    end: '2026-11-06T15:00',
    resource: 3,
    color: '#e67e22',
    attendees: []
  },
  {
    id: 'evt25',
    title: 'End of Day Review',
    start: '2026-11-06T16:00',
    end: '2026-11-06T18:00',
    resource: 4,
    color: '#34495e',
    attendees: []
  }
])

const myView: MbscEventcalendarView = {
  timeline: {
    type: 'week',
    startDay: 1,
    endDay: 5,
    startTime: '08:00',
    endTime: '18:00',
    timeCellStep: 60,
    timeLabelStep: 60,
    virtualScroll: false
  }
}

const dragElements = ref<HTMLElement[]>([])
const dropZoneEls = reactive<Record<string, HTMLElement | null>>({})
const dropStates = reactive<Record<string, string>>({})
const isExternalDragging = ref<boolean>(false)

const toastMessage = ref<string>('')
const toastColor = ref<string>('')
const isToastOpen = ref<boolean>(false)

const snackbarMessage = ref<string>('')
const snackbarButton = ref<{ text: string; action: () => void } | undefined>(undefined)
const isSnackbarOpen = ref<boolean>(false)

function getAssignmentCount(empId: string): number {
  return meetings.value.filter((m) => m.attendees.some((a) => a.id === empId)).length
}

function findConflict(empId: string, targetEventId: string): Meeting | null {
  const target = meetings.value.find((m) => m.id === targetEventId)
  if (!target) return null

  const targetStart = new Date(target.start as string).getTime()
  const targetEnd = new Date(target.end as string).getTime()

  for (const m of meetings.value) {
    if (m.id === targetEventId) continue
    if (!m.attendees.some((a) => a.id === empId)) continue
    const mStart = new Date(m.start as string).getTime()
    const mEnd = new Date(m.end as string).getTime()
    if (mStart < targetEnd && mEnd > targetStart) return m
  }
  return null
}

function setDropZoneEl(el: HTMLElement | null, eventId: string): void {
  dropZoneEls[eventId] = el
}

function showToast(message: string, color: string): void {
  toastMessage.value = message
  toastColor.value = color
  isToastOpen.value = true
}

function onDragStart(): void {
  const onMove = () => {
    isExternalDragging.value = true
    document.removeEventListener('pointermove', onMove)
  }
  const onUp = () => {
    isExternalDragging.value = false
    document.removeEventListener('pointermove', onMove)
    document.removeEventListener('pointerup', onUp)
  }
  document.addEventListener('pointermove', onMove)
  document.addEventListener('pointerup', onUp)
}

function onItemDrop(dropEvent: MbscItemDragEvent, eventId: string): void {
  const employee = dropEvent.data as Employee
  dropStates[eventId] = ''

  const meeting = meetings.value.find((m) => m.id === eventId)
  if (!meeting) return

  if (meeting.attendees.some((a) => a.id === employee.id)) {
    showToast(employee.name + ' is already assigned', 'danger')
    return
  }

  const conflict = findConflict(employee.id, eventId)
  if (conflict) {
    showToast(employee.name + ' already has a ' + conflict.title + ' on this timeslot', 'danger')
    return
  }

  meetings.value = meetings.value.map((m) =>
    m.id === eventId
      ? {
          ...m,
          attendees: [
            ...m.attendees,
            { id: employee.id, name: employee.name, avatar: employee.avatar, color: employee.color }
          ]
        }
      : m
  )

  showToast(employee.name + ' assigned to ' + meeting.title, 'success')
}

function onItemDragEnter(enterEvent: MbscItemDragEvent, eventId: string): void {
  const employee = enterEvent.data as Employee
  const meeting = meetings.value.find((m) => m.id === eventId)
  if (!meeting) return

  if (employee) {
    if (meeting.attendees.some((a) => a.id === employee.id)) {
      dropStates[eventId] = 'mds-drop-conflict'
    } else {
      const conflict = findConflict(employee.id, eventId)
      dropStates[eventId] = conflict ? 'mds-drop-conflict' : 'mds-drop-active'
    }
  } else {
    dropStates[eventId] = 'mds-drop-active'
  }
}

function onItemDragLeave(eventId: string): void {
  dropStates[eventId] = ''
}

function removeAttendee(eventId: string, empId: string, domEvent: Event): void {
  domEvent.stopPropagation()

  const meeting = meetings.value.find((m) => m.id === eventId)
  if (!meeting) return

  const idx = meeting.attendees.findIndex((a) => a.id === empId)
  if (idx === -1) return

  const removed = meeting.attendees[idx]

  meetings.value = meetings.value.map((m) =>
    m.id === eventId ? { ...m, attendees: m.attendees.filter((a) => a.id !== empId) } : m
  )

  snackbarMessage.value = removed.name + ' removed from ' + meeting.title
  snackbarButton.value = {
    text: 'Undo',
    action: () => {
      meetings.value = meetings.value.map((m) => {
        if (m.id !== eventId) return m
        const attendees = [...m.attendees]
        attendees.splice(idx, 0, removed)
        return { ...m, attendees }
      })
      showToast('Assignment restored', 'success')
    }
  }
  isSnackbarOpen.value = true
}
</script>

<template>
  <MbscPage
    :cssClass="`mds-drop-on-events${
      isExternalDragging ? ' mds-drop-on-events-external-dragging' : ''
    }`"
  >
    <div class="mbsc-grid mbsc-no-padding">
      <div class="mbsc-row">
        <div class="mbsc-col-sm-3 mbsc-flex-col mds-drop-on-events-sidebar">
          <div class="mbsc-form-group-title">Team Members</div>
          <div class="mds-drop-on-events-employee-list mbsc-flex">
            <template v-for="(emp, i) in employees" :key="emp.id">
              <div
                ref="dragElements"
                class="mds-drop-on-events-employee-item mbsc-flex"
                @pointerdown="onDragStart"
              >
                <div
                  class="mds-drop-on-events-employee-avatar mbsc-flex"
                  :style="{ background: emp.color }"
                >
                  {{ emp.avatar }}
                </div>
                <div class="mds-drop-on-events-employee-info mbsc-flex">
                  <div class="mds-drop-on-events-employee-name">{{ emp.name }}</div>
                  <div class="mds-drop-on-events-employee-count">
                    <template v-if="getAssignmentCount(emp.id) > 0">
                      {{ getAssignmentCount(emp.id) }} meeting{{
                        getAssignmentCount(emp.id) > 1 ? 's' : ''
                      }}
                    </template>
                    <template v-else>No assignments</template>
                  </div>
                </div>
                <MbscDraggable :element="dragElements[i]" :dragData="emp" />
              </div>
            </template>
          </div>
        </div>
        <div class="mbsc-col-sm-9 mds-drop-on-events-calendar-wrapper">
          <MbscEventcalendar
            :view="myView"
            :data="meetings"
            :resources="rooms"
            :dragToCreate="false"
            :dragToMove="false"
            :dragToResize="false"
            :clickToCreate="false"
            :eventDelete="false"
            :showEventTooltip="false"
          >
            <template #timelineEvent="data">
              <div
                :ref="(el) => setDropZoneEl(el, data.original.id)"
                class="mds-drop-on-events-custom-event mbsc-flex"
                :class="dropStates[data.original.id]"
                :style="{ borderLeft: '4px solid ' + data.original.color }"
              >
                <MbscDropcontainer
                  :element="dropZoneEls[data.original.id]"
                  @item-drop="onItemDrop($event, data.original.id)"
                  @item-drag-enter="onItemDragEnter($event, data.original.id)"
                  @item-drag-leave="onItemDragLeave(data.original.id)"
                >
                  <div class="mds-drop-on-events-event-header mbsc-flex">
                    <div class="mds-drop-on-events-event-title">{{ data.original.title }}</div>
                    <div class="mds-drop-on-events-event-time">
                      {{ data.start }} - {{ data.end }}
                    </div>
                  </div>
                  <div
                    v-if="data.original.attendees && data.original.attendees.length"
                    class="mds-drop-on-events-event-attendees mbsc-flex"
                  >
                    <span
                      v-for="attendee in data.original.attendees"
                      :key="attendee.id"
                      class="mds-drop-on-events-attendee-chip"
                      :style="{ background: attendee.color }"
                      :title="attendee.name + ' (click to remove)'"
                      @click="removeAttendee(data.original.id, attendee.id, $event)"
                    >
                      {{ attendee.avatar }}
                      <span class="mds-drop-on-events-attendee-remove">×</span>
                    </span>
                  </div>
                  <div class="mds-drop-on-events-event-drop-hint">Drop people to assign</div>
                </MbscDropcontainer>
              </div>
            </template>
          </MbscEventcalendar>
        </div>
      </div>
    </div>
    <MbscToast
      :message="toastMessage"
      :color="toastColor"
      :isOpen="isToastOpen"
      @close="isToastOpen = false"
    />
    <MbscSnackbar
      :message="snackbarMessage"
      :button="snackbarButton"
      :isOpen="isSnackbarOpen"
      @close="isSnackbarOpen = false"
    />
  </MbscPage>
</template>

<style>
.mds-drop-on-events,
.mds-drop-on-events .mbsc-grid,
.mds-drop-on-events .mbsc-row {
  height: 100%;
}
.mds-drop-on-events-sidebar {
  overflow-y: auto;
}
.mds-drop-on-events-employee-list {
  padding: 8px;
  flex-direction: column;
  gap: 4px;
}
.mds-drop-on-events-employee-item {
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: 10px;
  cursor: grab;
  user-select: none;
  background: rgba(128, 128, 128, 0.5);
  margin: 0 5px 5px 5px;
  touch-action: none;
  transition:
    background 0.15s,
    box-shadow 0.2s,
    transform 0.15s;
}
.mds-drop-on-events-employee-item:hover {
  background: rgba(128, 128, 128, 0.4);
  transform: translateY(-1px);
}
.mds-drop-on-events-employee-item:active {
  transform: translateY(0);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
.mds-drop-on-events-employee-avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  flex-shrink: 0;
  letter-spacing: 0.5px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}
.mds-drop-on-events-employee-info {
  flex-direction: column;
  overflow: hidden;
}
.mds-drop-on-events-employee-name {
  font-size: 15px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mds-drop-on-events-employee-count {
  font-size: 13px;
  opacity: 0.55;
}
.mds-drop-on-events-calendar-wrapper {
  border-left: 1px solid rgba(0, 0, 0, 0.1);
}
/* Drag clone is appended to body, outside the root — keep unscoped */
.mds-drop-on-events-employee-item.mbsc-drag-clone {
  opacity: 0.8;
}
.mds-drop-on-events-custom-event {
  background: #cccccc;
  border-radius: 6px;
  padding: 6px 8px;
  height: 100%;
  box-sizing: border-box;
  flex-direction: column;
  gap: 4px;
  overflow: hidden;
  outline: 2px solid transparent;
  transition: background 0.15s;
  position: relative;
}
.mds-drop-on-events-event-header {
  flex-direction: column;
  gap: 1px;
}
.mds-drop-on-events-event-title {
  font-size: 14px;
  font-weight: 600;
  color: #181818;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mds-drop-on-events-event-time {
  font-size: 12px;
  color: #545454;
}
.mds-drop-on-events-event-attendees {
  flex-wrap: wrap;
  gap: 3px;
}
/* Drop hint - hidden by default, shown only during external drag */
.mds-drop-on-events-event-drop-hint {
  display: none;
  font-size: 11px;
  font-style: italic;
  color: #686868;
}
/* Show drop hints and dashed borders on events during external drag */
.mds-drop-on-events-external-dragging .mds-drop-on-events-event-drop-hint {
  display: block;
}
.mds-drop-on-events-external-dragging .mds-drop-on-events-custom-event {
  outline: 2px dashed #b9b9b9;
}
.mds-drop-on-events-attendee-chip {
  display: flex;
  width: 25px;
  height: 25px;
  border-radius: 50%;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 9px;
  font-weight: 700;
  border: 2px solid rgba(255, 255, 255, 0.5);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
  cursor: pointer;
  position: relative;
}
.mds-drop-on-events-attendee-remove {
  display: none;
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  border-radius: 50%;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  line-height: 1;
}
.mds-drop-on-events-attendee-chip:hover .mds-drop-on-events-attendee-remove {
  display: flex;
}
.mds-drop-on-events-custom-event.mds-drop-active {
  cursor: copy;
  outline: 2px solid rgba(54, 133, 43, 0.8);
  background: rgba(180, 223, 173, 0.8);
}
.mds-drop-on-events-custom-event.mds-drop-conflict {
  cursor: not-allowed;
  outline: 2px solid rgba(145, 34, 34, 0.8);
  background: rgba(235, 194, 194, 0.8);
}
.mds-drop-on-events .mbsc-timeline-event {
  min-height: 80px;
}
.mds-drop-on-events .mbsc-schedule-event-inner {
  height: 100%;
}
</style>
```

## Implementation notes

- Use `type: 'week'` with `startDay: 1`, `endDay: 5`, `startTime: '08:00'`, `endTime: '18:00'`, `timeCellStep: 60`, `timeLabelStep: 60`, and `virtualScroll: false`.
- Define resources as a flat list of rooms. Disable all calendar-level interaction: set `dragToCreate`, `dragToMove`, `dragToResize`, `clickToCreate`, and `eventDelete` to `false`, and `showEventTooltip` to `false`.
- Render each employee item with a ref-callback to obtain its DOM element, then mount a `Draggable` with `dragData={emp}` and `element={elem}`. Show the employee's current assignment count (derived live from the meetings state) beneath their name.
- Track global drag state by attaching `pointermove` and `pointerup` listeners to `document` on each employee item's `onPointerDown`. Set an `isExternalDragging` boolean state on first move and clear it on pointer up. Apply this as a CSS class on the page wrapper to reveal the "Drop people to assign" hint and suppress Mobiscroll's built-in event hover overlays during drag.
- In `renderTimelineEvent` (Angular: `timelineEventTemplate`, Vue: `#timelineEvent` slot), render each meeting as a `MeetingEvent` component that uses a ref-callback for its root element and mounts a `Dropcontainer element={dropEl}` inside. Keep a local `dropState` string for visual feedback CSS classes.
- In `onItemDragEnter`, run the duplicate and conflict checks against `args.data` (the dragged employee) and set `dropState` to an active or conflict class accordingly. In `onItemDragLeave`, reset `dropState` to an empty string.
- In `onItemDrop`, check whether the employee is already in the event's `attendees` array (duplicate) and call `findConflict` to detect time-overlapping assignments across other meetings. Show an error `Toast` with `color: 'danger'` for either failure; call `onAssign` and show a success `Toast` on a valid drop.
- Implement `findConflict(empId, targetEventId)` by scanning all meetings for ones where the employee is already assigned and whose time range overlaps with the target event. Read from a `meetingsRef` (kept in sync via `useEffect`) rather than from state directly to avoid stale closure issues.
- On assignment, spread the employee object into the target event's `attendees` array via a state updater. Render current attendees as colored chips inside the event card; on chip click, call `stopPropagation`, remove the attendee from state, and open a `Snackbar` with an Undo button. On Undo, restore the attendee and show a success `Toast`.

## Run it in your project

1. If not signed up yet, or don't own a license, you can [start a free trial](https://mobiscroll.com/starttrial).

2. Clone the repository:

   ```
   git clone https://github.com/acidb/mobiscroll-demos-vue.git
   ```

3. If not installed yet, install the Mobiscroll CLI from NPM:

   ```
   npm install -g @mobiscroll/cli
   ```

4. Config Mobiscroll - open a terminal window in the root directory and run:

   ```
   mobiscroll config vue
   ```

5. Install dependencies - open a terminal window in the root directory and run:

   ```
   npm install
   ```

6. Run the project:

   ```
   npm run dev
   ```

On a trial, add `--trial` to the config command: `mobiscroll config vue --trial`.

Running a demo locally requires a Mobiscroll trial or a license; setup is per framework at https://mobiscroll.com/docs/vue/getting-started/installation

## Related demos

- Previous: [36-Hour Rolling Window Schedule](https://demo.mobiscroll.com/vue/timeline/36-hour-rolling-window-aircraft-view.md) - Build a fleet scheduling view on the Vue Timeline — a 36-hour rolling window that auto-advances from the current hour.
- Next: [Truck Dispatch Planning](https://demo.mobiscroll.com/vue/timeline/truck-transport-planning-and-dispatch.md) - Assign transports on the Vue timeline — hide trucks with capacity or time-window conflicts during drag and snap failed drops to the nearest valid slot.
