---
title: "Vue Timeline Task Progress Tracking Example | Mobiscroll"
component: "Timeline"
framework: "Vue"
library_version: "6.2.0"
url: "https://demo.mobiscroll.com/vue/timeline/show-task-progress-on-event"
---

# Task Progress Tracking - Vue Timeline demo

Show task completion progress on the Vue Timeline events with an inline hover slider for live updates and a progress field in the add/edit dialog. For Vue and Nuxt.

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

## Description

Showing percentage progress, or even having a visual overlay that indicates the progress of a task is sometimes necessary, not just for full fledged Gantt charts. Although being usually associated with Gantt charts, having a clear overview of progress with tools that enable live update is useful in a lot of situations.

Using the event templating capabilities, it is possible to render a live slider that reacts on hover and enables updating the progress inline. Use the [`timelineEvent`](https://mobiscroll.com/docs/vue/eventcalendar/timeline#slot-timelineEvent) slot to customize the template and adjust it to your needs.

Besides being able to update progress right on the event, it is also possible to add a slider that controls task progression - saved in a custom property of the event - inside an add/edit dialog.

## What this demo shows

- A desktop monthly timeline for project planning and progress tracking, with days arranged horizontally and resources listed vertically on the left.
- **Header navigation** The month and year label opens date navigation. The previous and next arrows and the Today button move between months and return to the current day.
- **Timeline header** The fixed date strip shows the days of the selected month in `D DDD` format, with the current date highlighted.
- **Resources** Hierarchical resource groups organize team members into departments such as Designer Team, Development Team, and QA Team.
- **Resource details** Individual resources show custom information, including the team member's role or job title.
- **Event colors** Events for the same team member or group keep a consistent color in the timeline.
- **Event cards** Events are rendered as colored cards with the project name, a progress percentage, and an inline progress slider.
- **Inline progress** The progress slider reacts on hover and lets users update completion directly on the event.
- **Event positioning** Events are placed by assigned resource and by start and end date.
- **Event creation** Users can create events by double-clicking a resource row or by clicking and dragging across a date range.
- **Add form** Creating an event opens a form with title, start date, end date, and progress slider fields, plus Cancel and Add actions.
- **Event editing** Clicking an event selects it and opens a popup for modifying the event details.
- **Drag and resize** Events show drag and resize handles on hover for moving tasks or changing their duration.
- **Vertical scrolling** The resource area scrolls vertically so the timeline can display more resources in the same view.

## Best for

- **Project management** Tracking tasks, ownership, timing, and completion progress in a single timeline.
- **Sprint planning** Planning work across team members and date ranges while keeping progress visible.
- **Software development teams** Coordinating designers, developers, and QA resources across overlapping project tasks.
- **Task tracking** Updating task progress directly from the timeline without opening a separate detail view.
- **Team workload management** Reviewing who is assigned to what, when each task runs, and how far along the work is.
- **Gantt-style planning** Showing project spans and completion percentages without requiring a full Gantt chart implementation.

## 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
- [`display`](https://mobiscroll.com/docs/vue/popup/api#opt-display) - option
- [`width`](https://mobiscroll.com/docs/vue/popup/api#opt-width) - option
- [`fullScreen`](https://mobiscroll.com/docs/vue/popup/api#opt-fullScreen) - option
- [`touchUi`](https://mobiscroll.com/docs/vue/popup/api#opt-touchUi) - option
- [`touchUi`](https://mobiscroll.com/docs/vue/datepicker/api#opt-touchUi) - option
- [`cssClass`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-cssClass) - 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
- [`clickToCreate`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-clickToCreate) - 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
- [`event-click`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventClick) - event
- [`event-created`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventCreated) - event
- [`event-deleted`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventDeleted) - event
- [`isOpen`](https://mobiscroll.com/docs/vue/popup/api#opt-isOpen) - option
- [`responsive`](https://mobiscroll.com/docs/vue/popup/api#opt-responsive) - option
- [`anchor`](https://mobiscroll.com/docs/vue/popup/api#opt-anchor) - option
- [`buttons`](https://mobiscroll.com/docs/vue/popup/api#opt-buttons) - option
- [`headerText`](https://mobiscroll.com/docs/vue/popup/api#opt-headerText) - option
- [`close`](https://mobiscroll.com/docs/vue/popup/api#event-onClose) - event
- [`select`](https://mobiscroll.com/docs/vue/datepicker/api#opt-select) - option
- [`responsive`](https://mobiscroll.com/docs/vue/datepicker/api#opt-responsive) - option
- [`startInput`](https://mobiscroll.com/docs/vue/datepicker/api#opt-startInput) - option
- [`endInput`](https://mobiscroll.com/docs/vue/datepicker/api#opt-endInput) - option
- [`showRangeLabels`](https://mobiscroll.com/docs/vue/datepicker/api#opt-showRangeLabels) - option

## Code

### SFC JS

```vue
<script setup>
import {
  MbscDatepicker,
  MbscEventcalendar,
  MbscInput,
  MbscPopup,
  setOptions
} from '@mobiscroll/vue'
import { ref } from 'vue'

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

const myEvents = ref([
  {
    start: '2026-10-31T00:00',
    end: '2026-11-03T00:00',
    title: 'Design Homepage',
    resource: 'alice',
    progress: 100
  },
  {
    start: '2026-10-31T00:00',
    end: '2026-11-04T00:00',
    title: 'Create Wireframes',
    resource: 'bob',
    progress: 100
  },
  {
    start: '2026-11-02T00:00',
    end: '2026-11-07T00:00',
    title: 'Develop Frontend',
    resource: 'charlie',
    progress: 45
  },
  {
    start: '2026-11-02T00:00',
    end: '2026-11-07T00:00',
    title: 'Develop Backend',
    resource: 'dave',
    progress: 35
  },
  {
    start: '2026-11-07T00:00',
    end: '2026-11-11T00:00',
    title: 'Test Website',
    resource: 'erin',
    progress: 0
  },
  {
    start: '2026-11-04T00:00',
    end: '2026-11-11T00:00',
    title: 'Fix Bugs',
    resource: 'frank',
    progress: 0
  },
  {
    start: '2026-11-11T00:00',
    end: '2026-11-14T00:00',
    title: 'Deploy Website',
    resource: 'george',
    progress: 0
  }
])

const myResources = ref([
  {
    id: 'gro1',
    name: 'Designer Team',
    color: '#76e083',
    eventCreation: false,
    children: [
      {
        id: 'alice',
        name: 'Alice',
        title: 'Designer',
        color: '#1dab2f'
      },
      {
        id: 'bob',
        name: 'Bob',
        title: 'Designer',
        color: '#76e083'
      }
    ]
  },
  {
    id: 'gro2',
    name: 'Development Team',
    color: '#ff1717',
    eventCreation: false,
    children: [
      {
        id: 'charlie',
        name: 'Charlie',
        title: 'Frontend Developer',
        color: '#4981d6'
      },
      {
        id: 'dave',
        name: 'Dave',
        title: 'Backend Developer',
        color: '#f7961e'
      },
      {
        id: 'frank',
        name: 'Frank',
        title: 'Full-Stack Developer',
        color: '#34c8e0'
      },
      {
        id: 'george',
        name: 'George',
        title: 'DevOps Engineer',
        color: '#e25dd2'
      }
    ]
  },

  {
    id: 'gro3',
    name: 'QA Team',
    color: '#d6d145',
    eventCreation: false,
    children: [
      {
        id: 'erin',
        name: 'Erin',
        title: 'QA Tester',
        color: '#d6d145'
      }
    ]
  }
])

const myView = {
  timeline: {
    type: 'month',
    eventDisplay: 'fill'
  }
}

let isEdit = false
let isDraggingProgress = false

const isPopupOpen = ref(false)

const popupEventTitle = ref('')
const popupEventDates = ref([])
const popupEventProgress = ref(0)
const popupEventResource = ref('')

const popupAnchor = ref(null)
const popupButtons = ref([])
const popupHeaderText = ref('')

const popupResponsive = {
  medium: {
    display: 'anchored',
    width: 400,
    fullScreen: false,
    touchUi: false
  }
}

const datePickerStartInput = ref(null)
const datePickerEndInput = ref(null)

const datePickerResponsive = {
  medium: {
    touchUi: false
  }
}

function loadPopupForm(event) {
  popupEventTitle.value = event.title
  popupEventDates.value = [event.start, event.end]
  popupEventResource.value = event.resource
  popupEventProgress.value = event.progress || 0
}

function createAddPopup(event, target) {
  isEdit = false
  popupHeaderText.value = 'New event'
  popupButtons.value = [
    'cancel',
    {
      text: 'Add',
      keyCode: 'enter',
      handler: () => {
        const newEvent = {
          id: event.id,
          title: popupEventTitle.value,
          start: popupEventDates.value[0],
          end: popupEventDates.value[1],
          resource: popupEventResource.value,
          progress: popupEventProgress.value
        }
        // Add the new event to the list
        myEvents.value = [...myEvents.value, newEvent]
        isPopupOpen.value = false
      },
      cssClass: 'mbsc-popup-button-primary'
    }
  ]
  loadPopupForm(event)
  popupAnchor.value = target
  isPopupOpen.value = true
}

function createEditPopup(event, target) {
  isEdit = true
  popupHeaderText.value = 'Edit event'
  popupButtons.value = [
    'cancel',
    {
      text: 'Save',
      keyCode: 'enter',
      handler: () => {
        const updatedEvent = { ...event }
        updatedEvent.title = popupEventTitle.value
        updatedEvent.start = popupEventDates.value[0]
        updatedEvent.end = popupEventDates.value[1]
        updatedEvent.resource = popupEventResource.value
        updatedEvent.progress = popupEventProgress.value
        // Update the event in the list
        const newEventList = [...myEvents.value]
        const index = newEventList.findIndex((e) => e.id === updatedEvent.id)
        newEventList[index] = updatedEvent
        myEvents.value = newEventList
        isPopupOpen.value = false
      },
      cssClass: 'mbsc-popup-button-primary'
    }
  ]
  loadPopupForm(event)
  popupAnchor.value = target
  isPopupOpen.value = true
}

function handleEventCreated(args) {
  createAddPopup(args.event, args.target)
}

function handleEventDeleted(args) {
  myEvents.value = myEvents.value.filter((e) => e.id !== args.event.id)
}

function handleEventClick(args) {
  if (isDraggingProgress) {
    return
  }
  createEditPopup(args.event, args.domEvent.currentTarget)
}

function handlePopupClose() {
  if (!isEdit) {
    // Refresh the list, if add popup was canceled, to remove the temporary event
    myEvents.value = [...myEvents.value]
  }
  isPopupOpen.value = false
}

const handleProgressArrowMouseDown = (e) => {
  const progressArrow = e.target.closest('.mds-progress-arrow')

  if (!progressArrow) {
    return
  }

  e.stopPropagation()

  isDraggingProgress = true

  const progressBar = progressArrow.closest('.mds-progress-bar')
  const progressLabel = progressArrow
    .closest('.mds-progress-event')
    .querySelector('.mds-progress-label')
  const eventContainerWidth = progressBar.parentElement.offsetWidth
  const initialMouseX = e.pageX
  const initialProgress = parseFloat(progressBar.style.width.replace('%', ''))

  let newProgress

  const handleMouseMove = (e) => {
    const mouseXOffset = e.pageX - initialMouseX

    newProgress = Math.round(initialProgress + (mouseXOffset / eventContainerWidth) * 100)
    newProgress = Math.max(0, Math.min(100, newProgress))

    progressBar.style.width = `${newProgress}%`
    progressLabel.textContent = `${newProgress}%`
  }

  const handleMouseUp = () => {
    document.removeEventListener('mousemove', handleMouseMove)
    document.removeEventListener('mouseup', handleMouseUp)

    const eventId = progressArrow.dataset.eventId
    const eventToUpdate = myEvents.value.find((event) => event.id === eventId)
    eventToUpdate.progress = newProgress

    setTimeout(() => {
      isDraggingProgress = false
    }, 100)
  }

  document.addEventListener('mousemove', handleMouseMove)
  document.addEventListener('mouseup', handleMouseUp)
}
</script>

<template>
  <div @mousedown.capture="handleProgressArrowMouseDown">
    <MbscEventcalendar
      cssClass="mds-progress-calendar"
      :view="myView"
      :data="myEvents"
      :resources="myResources"
      :clickToCreate="true"
      :dragToCreate="true"
      :dragToMove="true"
      :dragToResize="true"
      @event-click="handleEventClick"
      @event-created="handleEventCreated"
      @event-deleted="handleEventDeleted"
    >
      <template #timelineEvent="data">
        <div class="mds-progress-event" :style="{ background: data.color }">
          <div class="mds-progress-bar" :style="{ width: (data.original.progress || 0) + '%' }">
            <div class="mds-progress-arrow" :data-event-id="data.id"></div>
          </div>
          <div class="mds-progress-event-content">
            <div class="mds-progress-event-title">{{ data.title }}</div>
          </div>
          <div class="mds-progress-label" :key="data.original.progress || 0">
            {{ data.original.progress || 0 }}%
          </div>
        </div>
      </template>

      <template #resource="resource">
        <div>
          <div class="mds-progress-employee-name">{{ resource.name }}</div>
          <div v-if="resource.title" class="mds-progress-employee-title">{{ resource.title }}</div>
        </div>
      </template>
    </MbscEventcalendar>

    <MbscPopup
      display="bottom"
      :contentPadding="false"
      :fullScreen="true"
      :isOpen="isPopupOpen"
      :responsive="popupResponsive"
      :anchor="popupAnchor"
      :buttons="popupButtons"
      :headerText="popupHeaderText"
      @close="handlePopupClose"
    >
      <div class="mbsc-form-group">
        <MbscInput label="Title" v-model="popupEventTitle" />
      </div>
      <div class="mbsc-form-group">
        <MbscInput ref="datePickerStartInput" label="Starts" />
        <MbscInput ref="datePickerEndInput" label="Ends" />
        <MbscDatepicker
          v-model="popupEventDates"
          select="range"
          :responsive="datePickerResponsive"
          :startInput="datePickerStartInput"
          :endInput="datePickerEndInput"
          :showRangeLabels="false"
        />
      </div>
      <div class="mbsc-form-group">
        <label class="mbsc-flex mbsc-align-items-center mbsc-padding">
          <span>Progress</span>
          <input
            class="mds-popup-progress-slider mbsc-flex-1-0"
            type="range"
            min="0"
            max="100"
            v-model="popupEventProgress"
          />
          <span class="mds-popup-progress-label">{{ popupEventProgress }}%</span>
        </label>
      </div>
    </MbscPopup>
  </div>
</template>

<style>
.mds-progress-calendar .mbsc-timeline-row-gutter {
  height: 6px;
}

.mds-progress-calendar .mbsc-timeline-parent {
  height: 34px;
}

.mds-popup-progress-slider {
  margin: 0 10px;
}

.mds-popup-progress-label {
  margin-left: 10px;
  width: 50px;
  text-align: right;
}

.mds-progress-event {
  border-radius: 4px;
  position: relative;
  overflow: hidden;
  line-height: 19px;
}

.mds-progress-event-content {
  padding: 10px 0;
  margin-right: 50px;
}

.mds-progress-event-title {
  position: relative;
  color: white;
  font-size: 14px;
  padding: 0 10px;
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}

.mds-progress-bar {
  position: absolute;
  top: 0;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.4);
}

.mds-progress-arrow {
  position: absolute;
  right: -9px;
  bottom: 0;
  border-style: solid;
  border-width: 0 9px 9px 9px;
  border-color: transparent transparent rgba(255, 255, 255, 0.5) transparent;
  cursor: ew-resize;
}

.mds-progress-dragging {
  background-color: rgba(0, 0, 0, 0.5);
}

.mds-progress-dragging .mds-progress-arrow,
.mds-progress-arrow:hover {
  right: -12px;
  border-width: 0 12px 12px 12px;
  border-color: transparent transparent white transparent;
}

.mds-progress-label {
  position: absolute;
  right: 5px;
  top: 50%;
  margin-top: -10px;
  color: white;
  font-weight: bold;
  font-size: 14px;
}

.mds-progress-employee-name {
  font-size: 14px;
}

.mds-progress-employee-title {
  font-size: 12px;
  font-weight: 400;
  margin-top: 5px;
}
</style>
```

### SFC TS

```vue
<script setup lang="ts">
import {
  MbscDatepicker,
  MbscEventcalendar,
  MbscInput,
  MbscPopup,
  setOptions
} from '@mobiscroll/vue'
import type {
  MbscCalendarEvent,
  MbscDatepickerOptions,
  MbscDateType,
  MbscEventcalendarView,
  MbscEventClickEvent,
  MbscEventCreatedEvent,
  MbscEventDeletedEvent,
  MbscPopupButton,
  MbscPopupOptions
} from '@mobiscroll/vue'
import { ref } from 'vue'

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

const myEvents = ref<MbscCalendarEvent[]>([
  {
    start: '2026-10-31T00:00',
    end: '2026-11-03T00:00',
    title: 'Design Homepage',
    resource: 'alice',
    progress: 100
  },
  {
    start: '2026-10-31T00:00',
    end: '2026-11-04T00:00',
    title: 'Create Wireframes',
    resource: 'bob',
    progress: 100
  },
  {
    start: '2026-11-02T00:00',
    end: '2026-11-07T00:00',
    title: 'Develop Frontend',
    resource: 'charlie',
    progress: 45
  },
  {
    start: '2026-11-02T00:00',
    end: '2026-11-07T00:00',
    title: 'Develop Backend',
    resource: 'dave',
    progress: 35
  },
  {
    start: '2026-11-07T00:00',
    end: '2026-11-11T00:00',
    title: 'Test Website',
    resource: 'erin',
    progress: 0
  },
  {
    start: '2026-11-04T00:00',
    end: '2026-11-11T00:00',
    title: 'Fix Bugs',
    resource: 'frank',
    progress: 0
  },
  {
    start: '2026-11-11T00:00',
    end: '2026-11-14T00:00',
    title: 'Deploy Website',
    resource: 'george',
    progress: 0
  }
])

const myResources = ref([
  {
    id: 'gro1',
    name: 'Designer Team',
    color: '#76e083',
    eventCreation: false,
    children: [
      {
        id: 'alice',
        name: 'Alice',
        title: 'Designer',
        color: '#1dab2f'
      },
      {
        id: 'bob',
        name: 'Bob',
        title: 'Designer',
        color: '#76e083'
      }
    ]
  },
  {
    id: 'gro2',
    name: 'Development Team',
    color: '#ff1717',
    eventCreation: false,
    children: [
      {
        id: 'charlie',
        name: 'Charlie',
        title: 'Frontend Developer',
        color: '#4981d6'
      },
      {
        id: 'dave',
        name: 'Dave',
        title: 'Backend Developer',
        color: '#f7961e'
      },
      {
        id: 'frank',
        name: 'Frank',
        title: 'Full-Stack Developer',
        color: '#34c8e0'
      },
      {
        id: 'george',
        name: 'George',
        title: 'DevOps Engineer',
        color: '#e25dd2'
      }
    ]
  },

  {
    id: 'gro3',
    name: 'QA Team',
    color: '#d6d145',
    eventCreation: false,
    children: [
      {
        id: 'erin',
        name: 'Erin',
        title: 'QA Tester',
        color: '#d6d145'
      }
    ]
  }
])

const myView: MbscEventcalendarView = {
  timeline: {
    type: 'month',
    eventDisplay: 'fill'
  }
}

let isEdit = false
let isDraggingProgress = false

const isPopupOpen = ref(false)

const popupEventTitle = ref<string | undefined>('')
const popupEventDates = ref<MbscDateType[]>([])
const popupEventProgress = ref<number>(0)
const popupEventResource = ref<string | number | (string | number)[] | undefined>('')

const popupAnchor = ref<HTMLElement>()
const popupButtons = ref<(string | MbscPopupButton)[]>([])
const popupHeaderText = ref('')

const popupResponsive: { [key: string]: MbscPopupOptions } = {
  medium: {
    display: 'anchored',
    width: 400,
    fullScreen: false,
    touchUi: false
  }
}

const datePickerStartInput = ref<typeof MbscInput>()
const datePickerEndInput = ref<typeof MbscInput>()

const datePickerResponsive: { [key: string]: MbscDatepickerOptions } = {
  medium: {
    touchUi: false
  }
}

function loadPopupForm(event: MbscCalendarEvent) {
  popupEventTitle.value = event.title
  popupEventDates.value = [event.start as Date, event.end as Date]
  popupEventResource.value = event.resource
  popupEventProgress.value = event.progress || 0
}

function createAddPopup(event: MbscCalendarEvent, target?: HTMLElement) {
  isEdit = false
  popupHeaderText.value = 'New event'
  popupButtons.value = [
    'cancel',
    {
      text: 'Add',
      keyCode: 'enter',
      handler: () => {
        const newEvent: MbscCalendarEvent = {
          id: event.id,
          title: popupEventTitle.value,
          start: popupEventDates.value[0],
          end: popupEventDates.value[1],
          resource: popupEventResource.value,
          progress: popupEventProgress.value
        }
        // Add the new event to the list
        myEvents.value = [...myEvents.value, newEvent]
        isPopupOpen.value = false
      },
      cssClass: 'mbsc-popup-button-primary'
    }
  ]
  loadPopupForm(event)
  popupAnchor.value = target
  isPopupOpen.value = true
}

function createEditPopup(event: MbscCalendarEvent, target?: HTMLElement) {
  isEdit = true
  popupHeaderText.value = 'Edit event'
  popupButtons.value = [
    'cancel',
    {
      text: 'Save',
      keyCode: 'enter',
      handler: () => {
        const updatedEvent = { ...event }
        updatedEvent.title = popupEventTitle.value
        updatedEvent.start = popupEventDates.value[0]
        updatedEvent.end = popupEventDates.value[1]
        updatedEvent.resource = popupEventResource.value
        updatedEvent.progress = popupEventProgress.value
        // Update the event in the list
        const newEventList = [...myEvents.value]
        const index = newEventList.findIndex((x) => x.id === updatedEvent.id)
        newEventList[index] = updatedEvent
        myEvents.value = newEventList
        isPopupOpen.value = false
      },
      cssClass: 'mbsc-popup-button-primary'
    }
  ]
  loadPopupForm(event)
  popupAnchor.value = target
  isPopupOpen.value = true
}

function handleEventCreated(args: MbscEventCreatedEvent) {
  createAddPopup(args.event, args.target)
}

function handleEventDeleted(args: MbscEventDeletedEvent) {
  myEvents.value = myEvents.value.filter((e) => e.id !== args.event.id)
}

function handleEventClick(args: MbscEventClickEvent) {
  if (isDraggingProgress) {
    return
  }
  createEditPopup(args.event, args.domEvent.currentTarget)
}

function handlePopupClose() {
  if (!isEdit) {
    // Refresh the list, if add popup was canceled, to remove the temporary event
    myEvents.value = [...myEvents.value]
  }
  isPopupOpen.value = false
}

const handleProgressArrowMouseDown = (e: MouseEvent) => {
  const progressArrow = (e.target as HTMLDivElement).closest(
    '.mds-progress-arrow'
  ) as HTMLDivElement

  if (!progressArrow) {
    return
  }

  e.stopPropagation()

  isDraggingProgress = true

  const progressBar = progressArrow.closest('.mds-progress-bar') as HTMLDivElement
  const progressLabel = progressArrow
    .closest('.mds-progress-event')!
    .querySelector('.mds-progress-label') as HTMLDivElement
  const eventContainerWidth = progressBar.parentElement!.offsetWidth
  const initialMouseX = e.pageX
  const initialProgress = parseFloat(progressBar.style.width.replace('%', ''))

  let newProgress: number

  const handleMouseMove = (e: MouseEvent) => {
    const mouseXOffset = e.pageX - initialMouseX

    newProgress = Math.round(initialProgress + (mouseXOffset / eventContainerWidth) * 100)
    newProgress = Math.max(0, Math.min(100, newProgress))

    progressBar.style.width = `${newProgress}%`
    progressLabel.textContent = `${newProgress}%`
  }

  const handleMouseUp = () => {
    document.removeEventListener('mousemove', handleMouseMove)
    document.removeEventListener('mouseup', handleMouseUp)

    const eventId = progressArrow.dataset.eventId
    const eventToUpdate = myEvents.value.find((event) => event.id === eventId)!
    eventToUpdate.progress = newProgress

    setTimeout(() => {
      isDraggingProgress = false
    }, 100)
  }

  document.addEventListener('mousemove', handleMouseMove)
  document.addEventListener('mouseup', handleMouseUp)
}
</script>

<template>
  <div @mousedown.capture="handleProgressArrowMouseDown">
    <MbscEventcalendar
      cssClass="mds-progress-calendar"
      :view="myView"
      :data="myEvents"
      :resources="myResources"
      :clickToCreate="true"
      :dragToCreate="true"
      :dragToMove="true"
      :dragToResize="true"
      @event-click="handleEventClick"
      @event-created="handleEventCreated"
      @event-deleted="handleEventDeleted"
    >
      <template #timelineEvent="data">
        <div class="mds-progress-event" :style="{ background: data.color }">
          <div class="mds-progress-bar" :style="{ width: (data.original.progress || 0) + '%' }">
            <div class="mds-progress-arrow" :data-event-id="data.id"></div>
          </div>
          <div class="mds-progress-event-content">
            <div class="mds-progress-event-title">{{ data.title }}</div>
          </div>
          <div class="mds-progress-label" :key="data.original.progress || 0">
            {{ data.original.progress || 0 }}%
          </div>
        </div>
      </template>

      <template #resource="resource">
        <div>
          <div class="mds-progress-employee-name">{{ resource.name }}</div>
          <div v-if="resource.title" class="mds-progress-employee-title">{{ resource.title }}</div>
        </div>
      </template>
    </MbscEventcalendar>

    <MbscPopup
      display="bottom"
      :contentPadding="false"
      :fullScreen="true"
      :isOpen="isPopupOpen"
      :responsive="popupResponsive"
      :anchor="popupAnchor"
      :buttons="popupButtons"
      :headerText="popupHeaderText"
      @close="handlePopupClose"
    >
      <div class="mbsc-form-group">
        <MbscInput label="Title" v-model="popupEventTitle" />
      </div>
      <div class="mbsc-form-group">
        <MbscInput ref="datePickerStartInput" label="Starts" />
        <MbscInput ref="datePickerEndInput" label="Ends" />
        <MbscDatepicker
          v-model="popupEventDates"
          select="range"
          :responsive="datePickerResponsive"
          :startInput="datePickerStartInput"
          :endInput="datePickerEndInput"
          :showRangeLabels="false"
        />
      </div>
      <div class="mbsc-form-group">
        <label class="mbsc-flex mbsc-align-items-center mbsc-padding">
          <span>Progress</span>
          <input
            class="mds-popup-progress-slider mbsc-flex-1-0"
            type="range"
            min="0"
            max="100"
            v-model="popupEventProgress"
          />
          <span class="mds-popup-progress-label">{{ popupEventProgress }}%</span>
        </label>
      </div>
    </MbscPopup>
  </div>
</template>

<style>
.mds-progress-calendar .mbsc-timeline-row-gutter {
  height: 6px;
}

.mds-progress-calendar .mbsc-timeline-parent {
  height: 34px;
}

.mds-popup-progress-slider {
  margin: 0 10px;
}

.mds-popup-progress-label {
  margin-left: 10px;
  width: 50px;
  text-align: right;
}

.mds-progress-event {
  border-radius: 4px;
  position: relative;
  overflow: hidden;
  line-height: 19px;
}

.mds-progress-event-content {
  padding: 10px 0;
  margin-right: 50px;
}

.mds-progress-event-title {
  position: relative;
  color: white;
  font-size: 14px;
  padding: 0 10px;
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}

.mds-progress-bar {
  position: absolute;
  top: 0;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.4);
}

.mds-progress-arrow {
  position: absolute;
  right: -9px;
  bottom: 0;
  border-style: solid;
  border-width: 0 9px 9px 9px;
  border-color: transparent transparent rgba(255, 255, 255, 0.5) transparent;
  cursor: ew-resize;
}

.mds-progress-dragging {
  background-color: rgba(0, 0, 0, 0.5);
}

.mds-progress-dragging .mds-progress-arrow,
.mds-progress-arrow:hover {
  right: -12px;
  border-width: 0 12px 12px 12px;
  border-color: transparent transparent white transparent;
}

.mds-progress-label {
  position: absolute;
  right: 5px;
  top: 50%;
  margin-top: -10px;
  color: white;
  font-weight: bold;
  font-size: 14px;
}

.mds-progress-employee-name {
  font-size: 14px;
}

.mds-progress-employee-title {
  font-size: 12px;
  font-weight: 400;
  margin-top: 5px;
}
</style>
```

## Implementation notes

- Use `type: 'month'` with `eventDisplay: 'fill'` for a month-level Gantt-style view where tasks span multiple days.
- Give each event a custom `progress` property (0-100) representing task completion percentage.
- Define resources as a two-level tree of team groups (`eventCreation: false`) and individual members with a custom `title` (job role). Use `renderResource` (Angular: `resourceTemplate`, Vue: `#resource` slot) to show the member name and role beneath it.
- Use `renderTimelineEvent` (Angular: `timelineEventTemplate`, Vue: `#timelineEvent` slot) to render the progress bar layout: a container styled with the resource color, a progress fill div with `width` set to `${progress}%`, a draggable arrow handle element with a `data-event-id` attribute, the task title, and a percentage label.
- Implement inline progress dragging by attaching `onMouseDownCapture` on the calendar wrapper. On mousedown on the arrow handle, call `stopPropagation` to prevent Mobiscroll from treating it as an event click or drag, then attach `mousemove`/`mouseup` listeners to the document. On mousemove, compute new progress from the x-offset relative to the event container width. On mouseup, write the new value to the event in state and remove the listeners.
- Track drag state with an `isDraggingProgress` ref. In `onEventClick`, return early if `isDraggingProgress.current` is true so the edit popup does not open when the user finishes dragging the handle. Use a short `setTimeout` before resetting the ref to prevent the mouseup from immediately triggering a click.
- Enable `clickToCreate: true` and `dragToCreate: true` for event creation. Use `onEventCreated` to open the add popup with `args.target` as the anchor. Use `onEventClick` to open the edit popup with `args.domEvent.target` as the anchor. Use `onEventUpdated` to persist drag-to-move and drag-to-resize changes.
- Build the popup with `Popup`; default to full-screen bottom sheet on mobile and switch to an anchored 400px popup at the medium breakpoint.
- Include a `Datepicker` in range mode with `startInput`/`endInput` refs for editing the task date range, and a native `<input type="range">` (0-100) for updating the progress value directly in the form.
- On popup close when the add form is canceled, refresh the events list to remove the temporary placeholder event.

## 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: [Dual Synchronized Flight Scheduling](https://demo.mobiscroll.com/vue/timeline/flight-scheduling-two-synchronized-timelines.md) - Use synchronized Vue Timeline views to build a flight scheduling UI — drag flights to assign them to aircraft, with fixed event times and conflict detection.
- Next: [Shifts and Tasks](https://demo.mobiscroll.com/vue/timeline/tasks-subtasks-under-shifts.md) - Display shifts and linked tasks on a Vue Timeline — group tasks under parent shifts and restrict movement to each shift's time range.
