{"version":3,"file":"utils-21b7bf7b.js","sources":["../../src/views/appointments/constants.ts","../../src/views/appointments/utils.ts"],"sourcesContent":["export const TIME_FORMAT = 'HH:mm'\n","import type { Appointment, AppointmentLineItem, Branch, BranchConfiguration, Staff } from '@/types'\nimport type { Configuration } from '@/types/Configuration'\nimport { dayjs } from '@/plugins/dayjs'\nimport type { AppointmentForm } from './types/AppointmentForm'\nimport { TIME_FORMAT } from './constants'\nimport type { ConfigurationClosingDay } from '@/types/ConfigurationClosingDay'\n\nexport function getAppointmentDuration(appointment: Appointment | AppointmentForm) {\n  const lineItems = appointment.lineItems as AppointmentLineItem[]\n\n  return lineItems.reduce((acc, lineItem) => acc + lineItem.duration, 0)\n}\n\nexport function formatAppointmentTimeRange(\n  appointment: Appointment | AppointmentForm,\n  merchantTimezone: string\n) {\n  const durationInMinutes = getAppointmentDuration(appointment)\n\n  const start = dayjs(appointment.start).tz(merchantTimezone).format(TIME_FORMAT)\n  const end = dayjs(appointment.start)\n    .tz(merchantTimezone)\n    .add(durationInMinutes, 'minutes')\n    .format(TIME_FORMAT)\n\n  return `${start}-${end}`\n}\n\nexport function formatAppointmentDate(time: string | Date, merchantTimezone: string) {\n  return dayjs(time).tz(merchantTimezone).format('dddd DD.MM.YYYY')\n}\n\nexport function formatAppointmentDateTime(\n  appointment: Appointment | AppointmentForm,\n  merchantTimezone: string\n) {\n  const date = formatAppointmentDate(appointment.start!, merchantTimezone)\n  const timeRange = formatAppointmentTimeRange(appointment, merchantTimezone)\n\n  return `${date}, ${timeRange}`\n}\n\nexport function isUserInDifferentTimezone(merchantTimezone: string) {\n  return dayjs().tz(merchantTimezone).utcOffset() !== dayjs().utcOffset()\n}\n\nexport function getMerchantTimezoneFormatted(merchantTimezone: string) {\n  const timezoneFormatted = merchantTimezone.replace(/_/g, ' ')\n\n  const merchantTime = dayjs().tz(merchantTimezone)\n\n  if (merchantTime.utcOffset() === 0) {\n    return `${timezoneFormatted} (UTC)`\n  }\n\n  const offset = dayjs().tz(merchantTimezone).format('Z')\n\n  return `${timezoneFormatted} (UTC${offset})`\n}\n\nexport function isAppointmentEditingAllowed(\n  configuration: Configuration,\n  appointment: Appointment\n) {\n  const merchantTimezone = configuration.general.default_timezone\n  const cancellationLeadTime = configuration.reservations.customization.cancellation_lead_time\n\n  const now = dayjs().tz(merchantTimezone)\n  const appointmentTime = dayjs(appointment.start).tz(merchantTimezone)\n\n  if (now.isAfter(appointmentTime, 'minutes')) {\n    return false\n  }\n\n  const editingExpiresAt = appointmentTime.subtract(\n    cancellationLeadTime.value,\n    cancellationLeadTime.type\n  )\n\n  return now.isBefore(editingExpiresAt, 'minutes')\n}\n\nexport function getStaffName({ firstname, lastname }: Pick<Staff, 'firstname' | 'lastname'>) {\n  if (firstname && lastname) {\n    return `${firstname} ${lastname[0]}.`\n  }\n\n  return firstname || lastname\n}\n\nexport function getLineItemName(\n  lineItem: Pick<AppointmentLineItem, 'categoryName' | 'serviceName'>\n) {\n  const { serviceName, categoryName } = lineItem\n\n  return categoryName ? `${categoryName} - ${serviceName}` : serviceName\n}\n\nexport function isStaffMemberAssignedToBranch(staffMember: Staff, branch: Branch) {\n  return staffMember.locations === null || staffMember.locations.includes(branch.id)\n}\n\nfunction isDayClosed(\n  date: Date,\n  closingDays: ConfigurationClosingDay[],\n  localizedDayjs: typeof dayjs\n) {\n  const closingDaysMap: Record<string, boolean> = {}\n\n  closingDays.forEach(({ start_date, end_date }) => {\n    if (start_date === end_date) {\n      closingDaysMap[start_date] = true\n\n      return\n    }\n\n    /**\n     * If start / end days are not the same, we are working with a range.\n     * We want to index each day in the range for easy look ups\n     */\n    const start = localizedDayjs(start_date)\n    const end = localizedDayjs(end_date)\n\n    if (end.isBefore(start, 'date')) {\n      return\n    }\n\n    let current = start\n\n    while (current.isSameOrBefore(end, 'date')) {\n      closingDaysMap[current.format('YYYY-MM-DD')] = true\n\n      current = current.add(1, 'day')\n    }\n  })\n\n  return closingDaysMap[localizedDayjs(date).format('YYYY-MM-DD')] === true\n}\n\nfunction getOpenHoursForDate(\n  date: dayjs.Dayjs,\n  globalConfiguration: Configuration['reservations'],\n  branchConfiguration: BranchConfiguration['reservations'] | undefined,\n  localizedDayjs: typeof dayjs\n) {\n  const closedDays =\n    (branchConfiguration?.use_individual_closing_days\n      ? branchConfiguration.closing_days\n      : globalConfiguration.closing_days) || []\n\n  if (isDayClosed(date.toDate(), closedDays, localizedDayjs)) {\n    return 'closed'\n  }\n\n  const openingHours =\n    (branchConfiguration?.use_individual_opening_hours\n      ? branchConfiguration.opening_hours\n      : globalConfiguration.opening_hours) || []\n\n  // isoWeekday index is 1-based, but the API uses a 0-based index (0 = Monday, 6 = Sunday)\n  const dayIndex = date.isoWeekday() - 1\n\n  const hours = openingHours.find((day) => day.day_index === dayIndex)\n  return !hours || hours.closed ? 'closed' : hours\n}\n\nexport type OpenStatus = { isOpen: boolean, until?: string }\n\nexport function getTodaysOpenStatus(\n  globalConfiguration: Configuration['reservations'],\n  branchConfiguration: BranchConfiguration['reservations'] | undefined,\n  localizedDayjs: typeof dayjs\n): OpenStatus {\n  const today = localizedDayjs()\n  // Search for the next open day within the next week.\n  for (let offset = 0; offset < 8; offset++) {\n    const day = today.add(offset, 'day')\n    const hours = getOpenHoursForDate(day, globalConfiguration, branchConfiguration, localizedDayjs)\n    if (hours !== 'closed' && !hours.closed) {\n      // Found an open day.\n      if (day.isSame(today, 'day')) {\n        // Today has open hours.\n        const now = today.format('HH:mm')\n        if (now >= hours.open_from && now <= hours.open_to) {\n          // Currently open.\n          return { isOpen: true, until: hours.open_to }\n        }\n        if (now < hours.open_from) {\n          // Currently closed, but will open later today.\n          return { isOpen: false, until: hours.open_from }\n        }\n      } else {\n        // Currently closed, but we have found the next day that is open.\n        return { isOpen: false, until: `${day.format('dddd')}, ${hours.open_from}`}\n      }\n    }\n  }\n  // Closed today and we were unable to find an open day within the next week.\n  return { isOpen: false }\n}\n\nexport function getOpeningHours(\n  globalConfiguration: Configuration['reservations'],\n  branchConfiguration: BranchConfiguration['reservations'] | undefined,\n  localizedDayjs: typeof dayjs\n) {\n  const closedDays =\n    (branchConfiguration?.use_individual_closing_days\n      ? branchConfiguration.closing_days\n      : globalConfiguration.closing_days) || []\n\n  const openingHours =\n    (branchConfiguration?.use_individual_opening_hours\n      ? branchConfiguration.opening_hours\n      : globalConfiguration.opening_hours) || []\n\n  return openingHours.map((day) => {\n    const date = localizedDayjs().day(day.day_index + 1).toDate()\n    return {\n      day: localizedDayjs().day(day.day_index + 1).format('dddd'),\n      hours: day.closed ? null : `${day.open_from} - ${day.open_to}`,\n      isClosedDay: isDayClosed(date, closedDays, localizedDayjs),\n      isToday: dayjs().day() === day.day_index + 1\n    }\n  })\n}"],"names":["TIME_FORMAT","getAppointmentDuration","appointment","acc","lineItem","formatAppointmentTimeRange","merchantTimezone","durationInMinutes","start","dayjs","end","formatAppointmentDate","time","formatAppointmentDateTime","date","timeRange","isUserInDifferentTimezone","getMerchantTimezoneFormatted","timezoneFormatted","offset","isAppointmentEditingAllowed","configuration","cancellationLeadTime","now","appointmentTime","editingExpiresAt","getStaffName","firstname","lastname","getLineItemName","serviceName","categoryName","isStaffMemberAssignedToBranch","staffMember","branch","isDayClosed","closingDays","localizedDayjs","closingDaysMap","start_date","end_date","current","getOpenHoursForDate","globalConfiguration","branchConfiguration","closedDays","openingHours","dayIndex","hours","day","getTodaysOpenStatus","today","getOpeningHours"],"mappings":"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"}