Можно ли объединить watch вычисляемые свойства?
```
import { ref, computed, onMounted, watch } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import { useCompanyIdFromQuery, useTimelineNavigation } from '@/hooks';
import {
  isAfter,
  isBefore,
  formatDate,
  getMondayOfWeek,
  differenceBetweenDates,
  DateFormat,
} from '@/modules/date';
import type {
  BannerOptions,
  PromotionOptions,
  TimelineEntity,
  TimelineEntityType,
} from '@/components/TimelineEvents';
import type { RouteName } from '@/router/router.enums';
import type { ApiGetPromotionsTimelineParams } from '@/modules/promotion';
import type { ApiGetBannersTimelineParams } from '@/modules/banner';
import {
  calculateTimelineEventsPosition,
  getTimelineDays,
  getTimelineEntity,
  getTimelineHeaderTitle,
  isBannerOptions,
} from '@/modules/utils';

export type TimelineQueryFilters = {
  dateFrom?: string;
  [key: string]: string | string[] | number | number[] | undefined;
};

type TimelineConfig<T extends BannerOptions | PromotionOptions> = {
  entityType: TimelineEntityType;
  routeName: RouteName;
  apiFetch: (
    params: ApiGetPromotionsTimelineParams | ApiGetBannersTimelineParams,
  ) => Promise<{ data?: { content: T[] } }>;
  queryFilters: TimelineQueryFilters;
};

export function useTimeline<T extends BannerOptions | PromotionOptions>({
  entityType,
  routeName,
  apiFetch,
  queryFilters,
}: TimelineConfig<T>) {
  const router = useRouter();
  const { companyId } = useCompanyIdFromQuery();
  const { timelineDateFrom, handlePastDates, handleNextDates } = useTimelineNavigation(
    queryFilters,
    loadTimeline,
  );

  const rowHeight = 60;
  const eventsCount = ref(0);
  const events = ref<T[]>([]);

  const isDateFrom = computed(() => queryFilters.dateFrom !== undefined);
  const timelineHeaderDates = computed(() => getTimelineDays(timelineDateFrom.value));
  const timelineHeaderTitle = computed(() => getTimelineHeaderTitle(timelineHeaderDates.value));
  const positionedTimelineEvents = computed(() =>
    getPositionedTimelineEvents(
      events.value,
      entityType,
      timelineHeaderDates.value[0],
      timelineHeaderDates.value[6],
      rowHeight,
    ),
  );

  function getStartEndDate(entity: BannerOptions | PromotionOptions, type: TimelineEntityType) {
    if (isBannerOptions(entity, type)) {
      const { startDatetime, endDatetime } = entity.timings[0];
      return { startDatetime, endDatetime };
    } else {
      const { startDatetime, endDatetime } = entity.timing;
      return { startDatetime, endDatetime };
    }
  }

  function getPositionedTimelineEvents<T extends BannerOptions | PromotionOptions>(
    events: T[],
    timelineEntityType: TimelineEntityType,
    timelineStart: string,
    timelineEnd: string,
    rowHeight: number,
  ): { entity: TimelineEntity; style: Record<string, string> }[] {
    const timelineEvents: T[][] = [];
    const timelineData: { entity: TimelineEntity; style: Record<string, string> }[] = [];
    const timelineDayWidth = 100 / 7;
    const orderedTimelineEvents = [...events].sort((firstDate, secondDate) => {
      const firstDateTime = getStartEndDate(firstDate, timelineEntityType).startDatetime;
      const secondDateTime = getStartEndDate(secondDate, timelineEntityType).startDatetime;

      return differenceBetweenDates(firstDateTime, secondDateTime);
    });

    for (const orderedTimelineEvent of orderedTimelineEvents) {
      const { startDatetime, endDatetime } = getStartEndDate(
        orderedTimelineEvent,
        timelineEntityType,
      );

      if (isBefore(endDatetime, timelineStart) || isAfter(startDatetime, timelineEnd)) continue;

      const { startDiffDays, durationDays } = calculateTimelineEventsPosition(
        startDatetime,
        endDatetime,
        timelineStart,
      );

      let timelineEventIndex = timelineEvents.findIndex((timelineEvent) =>
        timelineEvent.every((event) => {
          const { startDatetime: eventStartDate, endDatetime: eventEndDate } = getStartEndDate(
            event,
            timelineEntityType,
          );
          return isBefore(eventEndDate, startDatetime) || isAfter(eventStartDate, endDatetime);
        }),
      );

      if (timelineEventIndex === -1) {
        timelineEventIndex = timelineEvents.length;
        timelineEvents[timelineEventIndex] = [];
      }

      timelineEvents[timelineEventIndex].push(orderedTimelineEvent);

      timelineData.push({
        entity: getTimelineEntity(orderedTimelineEvent, timelineEntityType),
        style: {
          left: `${startDiffDays * timelineDayWidth}%`,
          width: `${Math.max(durationDays * timelineDayWidth, 0.5)}%`,
          top: `${timelineEventIndex * rowHeight}px`,
        },
      });
    }

    return timelineData;
  }

  function getRequestParams() {
    return {
      companyId: Number(companyId.value),
      dateFrom: formatDate(timelineDateFrom.value, DateFormat.DATE_AND_TIME_MSK),
    };
  }

  async function loadTimeline(updatedQueryFilters?: TimelineQueryFilters) {
    if (!companyId.value) return;

    const filters = updatedQueryFilters || queryFilters;

    if (filters.dateFrom) {
      timelineDateFrom.value = filters.dateFrom;

      const { data } = await apiFetch({ ...getRequestParams(), ...filters });

      if (data) events.value = data.content;
      else ElMessage.error(`Error loading ${entityType} timeline.`);
    }
  }

  function handleClickTimelineEvent(id: number) {
    router.push({
      name: routeName,
      params: { id },
      query: { companyId: companyId.value },
    });
  }

  watch(
    positionedTimelineEvents,
    (timelineEvent) => {
      eventsCount.value = new Set(timelineEvent.map(({ style }) => style.top)).size;
    },
    { immediate: true },
  );

  watch(
    () => queryFilters,
    (newFilters) => {
      if (newFilters.dateFrom) loadTimeline(newFilters);
      else eventsCount.value = 0;
    },
    { deep: true },
  );

  onMounted(async () => {
    if (!queryFilters.dateFrom) {
      const monday = getMondayOfWeek();
      queryFilters.dateFrom = formatDate(monday, DateFormat.DATE_AND_TIME_MSK);
      timelineDateFrom.value = queryFilters.dateFrom;

      await loadTimeline();
    }
  });

  return {
    isDateFrom,
    timelineHeaderTitle,
    timelineHeaderDates,
    eventsCount,
    positionedTimelineEvents,
    rowHeight,
    handlePastDates,
    handleNextDates,
    handleClickTimelineEvent,
    loadTimeline,
  };
}
```