---
title: "Vue Timeline Resource Group Aggregate Summaries Example | Mobiscroll"
component: "Timeline"
framework: "Vue"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/vue/timeline/show-summaries-aggregates-for-resource-groups"
---

# Resource Group Aggregate Summaries - Vue Timeline demo

Show aggregate totals in parent resource rows on a Vue Timeline — summary values recalculate as bookings are added, changed, or removed. For Vue and Nuxt.

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

## Description

When scheduling across multiple groups of resources it sometimes is helpful to provide summaries and aggregate calculations on a group level. These dynamically calculated values can be presented as custom templated events in resource parent rows. Whenever new bookings are made, updated or deleted, use the [`event-created`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventCreated), [`event-update`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventUpdated) and [`event-deleted`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventDeleted) lifecycle events to update the aggregates for their parent resource. As we are working with variable event heights, make sure to set `eventHeight: 'variable'` which can be configured under the [`view`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-view). This is necessary if the actual booked events have a different height than the summary events (for parent resources).

**Variable event height is an experimental feature.** [Learn more about it in the documentation →](https://mobiscroll.com/docs/vue/eventcalendar/timeline#variable-event-height)

## What this demo shows

- A desktop timeline that combines a weekly date range with hierarchical resources and group-level summary events.
- **Header navigation** The month and year label opens date navigation, the previous and next arrows move between weeks, and the Today button returns to the current date.
- **Week view** The date strip shows the selected week from Sunday to Saturday, with the current date highlighted.
- **Resource hierarchy** The resource column shows expandable parent groups with nested vehicle resources grouped by fleet or location.
- **Resource details** Child resource rows display vehicle metadata such as model, license plate, and vehicle type.
- **Group summaries** Parent resource rows show daily aggregate metrics for all scheduled trips in that group.
- **Summary metrics** Each group summary displays total estimated distance, total fuel consumption, and estimated operating cost.
- **Event cards** Trip events show booking details such as destination or appointment, assigned driver, estimated distance, estimated fuel consumption, and pick-up and drop-off times.
- **Dynamic aggregates** Summary values are recalculated from the scheduled trips in each resource group when events are created, updated, or deleted.
- **Date positioning** Events are positioned by their assigned resource and exact date range.
- **Event interaction** Events highlight on hover.
- **Event selection** Clicking an event selects and highlights it.
- **Event creation** New events can be created with double click on timeline cells.
- **Scrolling** The timeline supports horizontal and vertical scrolling for navigating through time and resources.

## Best for

- **Fleet management** Scheduling vehicles while tracking daily distance, fuel consumption, and estimated operating cost by fleet or location.
- **Transportation planning** Comparing trip assignments across vehicles and resource groups in a timeline layout.
- **Vehicle reservations** Managing bookings for individual vehicles while keeping parent rows available for group-level utilization summaries.
- **Logistics operations** Reviewing scheduled trips alongside aggregate travel metrics for each resource group.
- **Field service scheduling** Assigning vehicles or crews to appointments where travel estimates and operating costs need to stay visible with the schedule.

## 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
- [`cssClass`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-cssClass) - option
- [`min`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-min) - option
- [`max`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-max) - option
- [`clickToCreate`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-clickToCreate) - option
- [`data`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-data) - 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
- [`eventOverlap`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-eventOverlap) - option
- [`extendDefaultEvent`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-extendDefaultEvent) - option
- [`resources`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-resources) - option
- [`view`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-view) - option
- [`event-created`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventCreated) - event
- [`event-deleted`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventDeleted) - event
- [`event-updated`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventUpdated) - event
- [`page-loading`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onPageLoading) - event

## Code

### SFC JS

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

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

let firstViewDay = null
let lastViewDay = null

const myEvents = ref([
  {
    id: 1,
    allDay: true,
    start: '2026-10-19T06:00',
    end: '2026-10-19T19:00',
    resource: 'orlando_1',
    title: 'Sales meeting with JLL',
    name: 'John C. Johnson',
    distance: 120,
    consumption: 35
  },
  {
    id: 2,
    allDay: true,
    start: '2026-10-20T03:00',
    end: '2026-10-20T20:00',
    resource: 'orlando_1',
    title: 'Client consultation',
    name: 'Mary K. Adams',
    distance: 95,
    consumption: 30
  },
  {
    id: 3,
    allDay: true,
    start: '2026-10-22T10:00',
    end: '2026-10-22T17:00',
    resource: 'orlando_1',
    title: 'Project review at XYZ',
    name: 'Robert B. Brown',
    distance: 150,
    consumption: 40
  },
  {
    id: 4,
    allDay: true,
    start: '2026-10-24T09:00',
    end: '2026-10-24T16:00',
    resource: 'orlando_1',
    title: 'Team building workshop',
    name: 'Susan E. Smith',
    distance: 60,
    consumption: 20
  },
  // Showing partial data. Download full source.
])

const myView = {
  timeline: {
    type: 'week',
    eventDisplay: 'fill',
    eventHeight: 'variable'
  }
}

const myResources = [
  {
    id: 'orlando',
    name: 'CNL Tower II',
    address: '420 S Orange Ave, Orlando, FL 32801',
    eventCreation: false,
    eventDragBetweenResources: false,
    eventDragInTime: false,
    eventResize: false,
    color: 'transparent',
    children: [
      {
        id: 'orlando_1',
        car: 'Lincoln Continental',
        plate: '51-5375',
        type: 'Sedan'
      },
      {
        id: 'orlando_2',
        car: 'Porsche Panamera',
        plate: '72-2590',
        type: 'Sports sedan'
      },
      {
        id: 'orlando_3',
        car: 'Range Rover Velar',
        plate: '41-7701',
        type: 'Compact SUV'
      }
    ]
  },
  // Showing partial data. Download full source.
]

const eventsWithSummaries = ref(myEvents.value)

function getAggregateEvents(dayEvents) {
  const aggregateEvents = {}
  myResources.forEach((resource) => {
    for (let date = new Date(firstViewDay); date < lastViewDay; date.setDate(date.getDate() + 1)) {
      aggregateEvents[resource.id + +date] = {
        id: resource.id + +date,
        isSummary: true,
        allDay: true,
        start: new Date(date),
        end: new Date(date),
        consumption: 0,
        distance: 0,
        resource: resource.id
      }
    }
  })
  dayEvents.forEach((event) => {
    if (!event.isSummary) {
      const parentResource = event.resource.split('_')[0]
      const dayStart = new Date(event.start).setHours(0, 0, 0, 0)
      if (aggregateEvents[parentResource + dayStart]) {
        aggregateEvents[parentResource + dayStart].consumption += event.consumption
        aggregateEvents[parentResource + dayStart].distance += event.distance
      }
    }
  })
  return Object.values(aggregateEvents)
}

function updateCalendarEvents(inst) {
  const dailyEvents = inst.getEvents()
  const updatedSummaries = getAggregateEvents(dailyEvents)
  eventsWithSummaries.value = [...myEvents.value, ...updatedSummaries]
}

function handleDefaultEvent(args) {
  const d = args.start
  const start = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 9)
  const end = new Date(start.getFullYear(), start.getMonth(), start.getDate(), 18)
  return {
    allDay: true,
    start: start,
    end: end,
    title: 'New event',
    name: 'John Doe',
    distance: 42,
    consumption: 6
  }
}

function handlePageLoading(args, inst) {
  firstViewDay = new Date(args.firstDay)
  lastViewDay = new Date(args.lastDay)
  updateCalendarEvents(inst)
}

function handleEventUpdated(args, inst) {
  updateCalendarEvents(inst)
}

function handleEventCreated(args, inst) {
  const newEvent = args.event
  myEvents.value.push(newEvent)
  updateCalendarEvents(inst)
}

function handleEventDeleted(args, inst) {
  const deletedEvent = args.event
  myEvents.value = myEvents.value.filter((e) => deletedEvent.id !== e.id)
  updateCalendarEvents(inst)
}
</script>

<template>
  <MbscEventcalendar
    cssClass="mds-aggregates"
    :min="dyndatetime('y,m,d-7')"
    :max="dyndatetime('y,m,d+6')"
    :clickToCreate="true"
    :data="eventsWithSummaries"
    :dragToCreate="false"
    :dragToMove="true"
    :dragToResize="false"
    :eventOverlap="false"
    :extendDefaultEvent="handleDefaultEvent"
    :resources="myResources"
    :view="myView"
    @event-created="handleEventCreated"
    @event-deleted="handleEventDeleted"
    @event-updated="handleEventUpdated"
    @page-loading="handlePageLoading"
  >
    <template #resource="resource">
      <template v-if="resource.isParent">
        <div>{{ resource.name }}</div>
        <div class="mds-aggregates-address">{{ resource.address }}</div>
      </template>
      <template v-else>
        <div>{{ resource.car }}</div>
        <div class="mds-aggregates-plate">Plate {{ resource.plate }}</div>
        <div class="mds-aggregates-cartype">{{ resource.type }}</div>
      </template>
    </template>
    <template #timelineEventContent="event">
      <template v-if="event.currentResource.isParent">
        <div class="mds-aggregates-summary-event mbsc-flex">
          <div class="mds-aggregates-summary-details mbsc-flex mbsc-flex-1-1">
            <div class="mds-aggregates-summary-label">
              Total dist.
              <div class="mds-aggregates-summary-value">{{ event.original.distance }} mi</div>
            </div>
            <div class="mds-aggregates-summary-label">
              Total cons.
              <div class="mds-aggregates-summary-value">{{ event.original.consumption }} gal</div>
            </div>
            <div class="mds-aggregates-summary-label">
              Est. cost
              <div class="mds-aggregates-summary-value">
                $
                {{ Math.round(event.original.consumption * 3.706 || 0) }}
              </div>
            </div>
          </div>
        </div>
      </template>
      <template v-else>
        <div class="mds-aggregates-event">
          <div class="mds-aggregates-title">
            {{ event.title }}
          </div>
          <div class="mds-aggregates-name">
            {{ event.original.name }}
          </div>
          <div class="mds-aggregates-details">
            <div class="mds-aggregates-label">
              Est. distance:
              <span class="mds-aggregates-value"> {{ event.original.distance }} mi </span>
            </div>
            <div class="mds-aggregates-label">
              Est. consumption:
              <span class="mds-aggregates-value">{{ event.original.consumption }} gal </span>
            </div>
          </div>
          <div class="mds-aggregates-label mbsc-flex">
            <div class="mbsc-flex-1-1">
              Pick up:
              <span class="mds-aggregates-value">
                {{ formatDate('hh:mm A', new Date(event.original.start)) }}
              </span>
            </div>
            <div>
              Drop off:
              <span class="mds-aggregates-value">
                {{ formatDate('hh:mm A', new Date(event.original.end)) }}
              </span>
            </div>
          </div>
        </div>
      </template>
    </template>
  </MbscEventcalendar>
</template>

<style>
.mds-aggregates .mbsc-timeline-column,
.mds-aggregates .mbsc-timeline-header-column {
  width: 14.5em;
}

.mds-aggregates .mbsc-timeline-row-gutter {
  height: 6px;
}

.mds-aggregates .mbsc-timeline-resource-title {
  font-size: 14px;
  font-weight: 600;
}

.mds-aggregates .mbsc-ios .mbsc-schedule-event:after {
  display: none;
}

.mds-aggregates .mbsc-schedule-event-inner {
  height: auto;
  width: 100%;
}

.mbsc-windows-dark .mds-aggregates-summary-event,
.mbsc-ios-dark .mds-aggregates-summary-event,
.mbsc-material-dark .mds-aggregates-summary-event {
  color: #fff;
}

.mds-aggregates-address {
  padding: 8px 0;
  font-size: 11px;
  line-height: 16px;
  font-weight: 600;
}

.mds-aggregates-plate {
  padding: 8px 0;
  font-weight: 600;
}

.mds-aggregates-cartype {
  font-size: 11px;
  font-weight: 600;
}

.mds-aggregates-summary-event {
  color: #000;
}

.mds-aggregates-summary-details {
  white-space: normal;
  justify-content: space-between;
  padding: 8px;
}

.mds-aggregates-summary-label {
  font-size: 11px;
  font-weight: 400;
}

.mds-aggregates-summary-value {
  font-weight: 600;
  font-size: 15px;
  line-height: 24px;
}

.mds-aggregates-event {
  padding: 4px;
}

.mds-aggregates-title {
  white-space: normal;
  line-height: 20px;
  font-size: 14px;
  font-weight: 600;
}

.mds-aggregates-name {
  padding: 4px 0;
  font-size: 12px;
  line-height: 16px;
  font-weight: 600;
}

.mds-aggregates-value {
  font-weight: 600;
}

.mds-aggregates-label {
  font-size: 11px;
  font-weight: 400;
}

.mds-aggregates-details {
  white-space: normal;
  justify-content: space-between;
  padding: 8px 0;
}
</style>
```

### SFC TS

```vue
<script setup lang="ts">
import { formatDate, MbscEventcalendar, setOptions } from '@mobiscroll/vue'
import type {
  MbscCalendarEvent,
  MbscEventcalendarView,
  MbscEventCreatedEvent,
  MbscEventDeletedEvent,
  MbscEventUpdatedEvent,
  MbscNewEventData,
  MbscPageLoadingEvent,
  MbscResource
} from '@mobiscroll/vue'
import { ref } from 'vue'

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

let firstViewDay: Date = new Date()
let lastViewDay: Date = new Date()

const myEvents = ref<MbscCalendarEvent[]>([
  {
    id: 1,
    allDay: true,
    start: '2026-10-19T06:00',
    end: '2026-10-19T19:00',
    resource: 'orlando_1',
    title: 'Sales meeting with JLL',
    name: 'John C. Johnson',
    distance: 120,
    consumption: 35
  },
  {
    id: 2,
    allDay: true,
    start: '2026-10-20T03:00',
    end: '2026-10-20T20:00',
    resource: 'orlando_1',
    title: 'Client consultation',
    name: 'Mary K. Adams',
    distance: 95,
    consumption: 30
  },
  {
    id: 3,
    allDay: true,
    start: '2026-10-22T10:00',
    end: '2026-10-22T17:00',
    resource: 'orlando_1',
    title: 'Project review at XYZ',
    name: 'Robert B. Brown',
    distance: 150,
    consumption: 40
  },
  {
    id: 4,
    allDay: true,
    start: '2026-10-24T09:00',
    end: '2026-10-24T16:00',
    resource: 'orlando_1',
    title: 'Team building workshop',
    name: 'Susan E. Smith',
    distance: 60,
    consumption: 20
  },
  // Showing partial data. Download full source.
])

const myView: MbscEventcalendarView = {
  timeline: {
    type: 'week',
    eventDisplay: 'fill',
    eventHeight: 'variable'
  }
}

const myResources: MbscResource[] = [
  {
    id: 'orlando',
    name: 'CNL Tower II',
    address: '420 S Orange Ave, Orlando, FL 32801',
    eventCreation: false,
    eventDragBetweenResources: false,
    eventDragInTime: false,
    eventResize: false,
    color: 'transparent',
    children: [
      {
        id: 'orlando_1',
        car: 'Lincoln Continental',
        plate: '51-5375',
        type: 'Sedan'
      },
      {
        id: 'orlando_2',
        car: 'Porsche Panamera',
        plate: '72-2590',
        type: 'Sports sedan'
      },
      {
        id: 'orlando_3',
        car: 'Range Rover Velar',
        plate: '41-7701',
        type: 'Compact SUV'
      }
    ]
  },
  // Showing partial data. Download full source.
]

const eventsWithSummaries = ref<MbscCalendarEvent[]>(myEvents.value)

function getAggregateEvents(dayEvents: MbscCalendarEvent[]): MbscCalendarEvent[] {
  const aggregateEvents: { [key: string]: MbscCalendarEvent } = {}
  myResources.forEach((resource) => {
    for (let date = new Date(firstViewDay); date < lastViewDay; date.setDate(date.getDate() + 1)) {
      aggregateEvents[(resource.id as string) + +date] = {
        id: (resource.id as string) + +date,
        isSummary: true,
        allDay: true,
        start: new Date(date),
        end: new Date(date),
        consumption: 0,
        distance: 0,
        resource: resource.id
      }
    }
  })
  dayEvents.forEach((event) => {
    if (!event.isSummary) {
      const parentResource = (event.resource as string).split('_')[0]
      const dayStart = new Date(event.start as Date).setHours(0, 0, 0, 0)
      if (aggregateEvents[parentResource + dayStart]) {
        aggregateEvents[parentResource + dayStart].consumption += event.consumption
        aggregateEvents[parentResource + dayStart].distance += event.distance
      }
    }
  })
  return Object.values(aggregateEvents)
}

function updateCalendarEvents(inst: any) {
  const dailyEvents = inst.getEvents()
  const updatedSummaries = getAggregateEvents(dailyEvents)
  eventsWithSummaries.value = [...myEvents.value, ...updatedSummaries]
}

function handleDefaultEvent(args: MbscNewEventData) {
  const d = args.start
  const start = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 9)
  const end = new Date(start.getFullYear(), start.getMonth(), start.getDate(), 18)
  return {
    allDay: true,
    start: start,
    end: end,
    title: 'New event',
    name: 'John Doe',
    distance: 42,
    consumption: 6
  }
}

function handlePageLoading(args: MbscPageLoadingEvent, inst: any): void {
  firstViewDay = new Date(args.firstDay)
  lastViewDay = new Date(args.lastDay)
  updateCalendarEvents(inst)
}

function handleEventUpdated(args: MbscEventUpdatedEvent, inst: any): void {
  updateCalendarEvents(inst)
}

function handleEventCreated(args: MbscEventCreatedEvent, inst: any): void {
  const newEvent = args.event
  myEvents.value.push(newEvent)
  updateCalendarEvents(inst)
}

function handleEventDeleted(args: MbscEventDeletedEvent, inst: any): void {
  const deletedEvent = args.event
  myEvents.value = myEvents.value.filter((e) => deletedEvent.id !== e.id)
  updateCalendarEvents(inst)
}
</script>

<template>
  <MbscEventcalendar
    cssClass="mds-aggregates"
    :min="dyndatetime('y,m,d-7')"
    :max="dyndatetime('y,m,d+6')"
    :clickToCreate="true"
    :data="eventsWithSummaries"
    :dragToCreate="false"
    :dragToMove="true"
    :dragToResize="false"
    :eventOverlap="false"
    :extendDefaultEvent="handleDefaultEvent"
    :resources="myResources"
    :view="myView"
    @event-created="handleEventCreated"
    @event-deleted="handleEventDeleted"
    @event-updated="handleEventUpdated"
    @page-loading="handlePageLoading"
  >
    <template #resource="resource">
      <template v-if="resource.isParent">
        <div>{{ resource.name }}</div>
        <div class="mds-aggregates-address">{{ resource.address }}</div>
      </template>
      <template v-else>
        <div>{{ resource.car }}</div>
        <div class="mds-aggregates-plate">Plate {{ resource.plate }}</div>
        <div class="mds-aggregates-cartype">{{ resource.type }}</div>
      </template>
    </template>
    <template #timelineEventContent="event">
      <template v-if="event.currentResource.isParent">
        <div class="mds-aggregates-summary-event mbsc-flex">
          <div class="mds-aggregates-summary-details mbsc-flex mbsc-flex-1-1">
            <div class="mds-aggregates-summary-label">
              Total dist.
              <div class="mds-aggregates-summary-value">{{ event.original.distance }} mi</div>
            </div>
            <div class="mds-aggregates-summary-label">
              Total cons.
              <div class="mds-aggregates-summary-value">{{ event.original.consumption }} gal</div>
            </div>
            <div class="mds-aggregates-summary-label">
              Est. cost
              <div class="mds-aggregates-summary-value">
                $
                {{ Math.round(event.original.consumption * 3.706 || 0) }}
              </div>
            </div>
          </div>
        </div>
      </template>
      <template v-else>
        <div class="mds-aggregates-event">
          <div class="mds-aggregates-title">
            {{ event.title }}
          </div>
          <div class="mds-aggregates-name">
            {{ event.original.name }}
          </div>
          <div class="mds-aggregates-details">
            <div class="mds-aggregates-label">
              Est. distance:
              <span class="mds-aggregates-value"> {{ event.original.distance }} mi </span>
            </div>
            <div class="mds-aggregates-label">
              Est. consumption:
              <span class="mds-aggregates-value">{{ event.original.consumption }} gal </span>
            </div>
          </div>
          <div class="mds-aggregates-label mbsc-flex">
            <div class="mbsc-flex-1-1">
              Pick up:
              <span class="mds-aggregates-value">
                {{ formatDate('hh:mm A', new Date(event.original.start)) }}
              </span>
            </div>
            <div>
              Drop off:
              <span class="mds-aggregates-value">
                {{ formatDate('hh:mm A', new Date(event.original.end)) }}
              </span>
            </div>
          </div>
        </div>
      </template>
    </template>
  </MbscEventcalendar>
</template>

<style>
.mds-aggregates .mbsc-timeline-column,
.mds-aggregates .mbsc-timeline-header-column {
  width: 14.5em;
}

.mds-aggregates .mbsc-timeline-row-gutter {
  height: 6px;
}

.mds-aggregates .mbsc-timeline-resource-title {
  font-size: 14px;
  font-weight: 600;
}

.mds-aggregates .mbsc-ios .mbsc-schedule-event:after {
  display: none;
}

.mds-aggregates .mbsc-schedule-event-inner {
  height: auto;
  width: 100%;
}

.mbsc-windows-dark .mds-aggregates-summary-event,
.mbsc-ios-dark .mds-aggregates-summary-event,
.mbsc-material-dark .mds-aggregates-summary-event {
  color: #fff;
}

.mds-aggregates-address {
  padding: 8px 0;
  font-size: 11px;
  line-height: 16px;
  font-weight: 600;
}

.mds-aggregates-plate {
  padding: 8px 0;
  font-weight: 600;
}

.mds-aggregates-cartype {
  font-size: 11px;
  font-weight: 600;
}

.mds-aggregates-summary-event {
  color: #000;
}

.mds-aggregates-summary-details {
  white-space: normal;
  justify-content: space-between;
  padding: 8px;
}

.mds-aggregates-summary-label {
  font-size: 11px;
  font-weight: 400;
}

.mds-aggregates-summary-value {
  font-weight: 600;
  font-size: 15px;
  line-height: 24px;
}

.mds-aggregates-event {
  padding: 4px;
}

.mds-aggregates-title {
  white-space: normal;
  line-height: 20px;
  font-size: 14px;
  font-weight: 600;
}

.mds-aggregates-name {
  padding: 4px 0;
  font-size: 12px;
  line-height: 16px;
  font-weight: 600;
}

.mds-aggregates-value {
  font-weight: 600;
}

.mds-aggregates-label {
  font-size: 11px;
  font-weight: 400;
}

.mds-aggregates-details {
  white-space: normal;
  justify-content: space-between;
  padding: 8px 0;
}
</style>
```

## Implementation notes

- Use `timeline: { type: 'week', eventDisplay: 'fill', eventHeight: 'variable' }`. `eventHeight: 'variable'` is required because summary events on parent rows and regular events on child rows have different heights.
- Restrict the calendar to a 2-week window centered on today: `min: dyndatetime('y,m,d-7')`, `max: dyndatetime('y,m,d+6')`.
- Define 3 parent resource groups (by city), each with 3 child vehicle resources. Use city-prefixed IDs to establish parent-child relationships (e.g. `orlando`, `orlando_1`, `orlando_2`, `orlando_3`). Parent resources carry `name`, `address`, `color: 'transparent'`, and `eventCreation: false`, `eventDragBetweenResources: false`, `eventDragInTime: false`, `eventResize: false` - they display summary data only and cannot be booked directly. Child resources carry `car`, `plate`, and `type` as custom properties.
- Add ~66 regular events (all `allDay: true`), each assigned to a child resource. Every event carries two custom numeric properties: `distance` (miles) and `consumption` (gallons). Use `dyndatetime` offsets relative to today for dates.
- Enable `clickToCreate: true`, `dragToMove: true`. Set `dragToCreate: false`, `dragToResize: false`, `eventOverlap: false`. Provide `extendDefaultEvent` to supply defaults for newly created events (`title: 'New event'`, `name: 'John Doe'`, `distance: 42`, `consumption: 6`, `allDay: true`).
- **Summary (aggregate) events** - generated programmatically, not from user input:
  - For each visible day and each parent resource, create one summary event object: `{ id: parentId + dayTimestamp, isSummary: true, allDay: true, start: day, end: day, resource: parentId, distance: 0, consumption: 0 }`.
  - Iterate over all non-summary events; for each, derive the parent resource ID by splitting the child resource ID on `_` (e.g. `'orlando_1'.split('_')[0]` → `'orlando'`). Add the event's `distance` and `consumption` to the matching (parent, day) summary object.
  - Estimated cost is computed in the render layer: `Math.round(consumption * 3.706)`.
  - Merge the original events and the generated summaries into a single array and pass it to the calendar's `data` prop.
- **Refresh cycle**: Store original events in a mutable ref (not state). On `onPageLoading`, capture `args.firstDay` / `args.lastDay`, then call the aggregate recalculation in a `setTimeout`. On `onEventCreated`, `onEventUpdated`, and `onEventDeleted`, update the events ref and call the same recalculation.
- **Custom resource column** (`renderResource` / `resourceTemplate` / `resource` slot): check `res.isParent` - if true, render `name` + `address`; otherwise render `car`, licence plate, and vehicle type.
- **Custom event content** (`renderTimelineEventContent` / `timelineEventContentTemplate` / `timeline-event-content` slot): check `event.currentResource.isParent` - if true (summary row), render "Total dist.", "Total cons.", "Est. cost" aggregates; otherwise render the booking title, driver name, estimated distance/consumption, and formatted pick-up / drop-off times using `formatDate('hh:mm A', ...)`.

## 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: [Resource Grouping & Hierarchy](https://demo.mobiscroll.com/vue/timeline/resource-grouping-hierarchy.md) - Build a Vue resource timeline with collapsible nested groups, pinned resources, and per-group event creation controls.
- Next: [Resource Drag-and-Drop Reorder](https://demo.mobiscroll.com/vue/timeline/resource-drag-drop-reorder.md) - Reorder resources on a Vue Timeline by drag and drop — each row shows a drag handle, and an update callback runs on drop.
