<i18n>
{
  "ru": {
    "SlotsCalendar": {
      "daysOfWeek": {
        "0": "Вс",
        "1": "Пн",
        "2": "Вт",
        "3": "Ср",
        "4": "Чт",
        "5": "Пт",
        "6": "Сб"
      },
      "buttons": {
        "ok": "Применить",
        "cancel": "Отменить"
      },
      "downloadTickets": "Выгрузить записи на прием",
      "ThereSNoTickets": "Пока нет записей на прием для выгрузки",
      "downloadError": "Не удалось загрузить таблицу слотов",
      "statementHasNoFound": "Не удвлось найти заявление",
      "goToTheStatement": "Перейти к заявлению",
      "statement": "Заявление",
      "snils": "СНИЛС",
      "markAsServed": "Пометить обслуженным",
      "markAsBooked": "Пометить забронированным",
      "cancelBooking": "Отменить бронирование",
      "deleteAllSlots": "Удалить все слоты",
      "deleteSlot": "Удалить слот",
      "cancelAllBookings": "Отменить все бронирования",
      "status": {
        "101": "Зарезервировано",
        "201": "Забронировано",
        "401": "Обслужено",
        "301": "Отменено"
      },
      "plural": {
        "slot": "Нет свободных слотов | {n} слот | {n} слота | {n} слотов"
      },
      "labels": {
        "fullName": "Посетитель",
        "reservedUntil": "Время резервации",
        "caseNumber": "Заявление",
        "actions": "Действия"
      }
    }
  },
  "en": {
    "SlotsCalendar": {
      "daysOfWeek": {
        "0": "Su",
        "1": "Mo",
        "2": "Tu",
        "3": "We",
        "4": "Th",
        "5": "Fr",
        "6": "St"
      },
      "buttons": {
        "ok": "Ok",
        "cancel": "Cancel"
      },
      "downloadTickets": "Download tickets",
      "ThereSNoTickets": "There's no tickets yet",
      "downloadError": "Failed to download slots table!",
      "statementHasNoFound": "Statement has no found",
      "goToTheStatement": "Go to the statement",
      "statement": "Statement id",
      "snils": "SNILS",
      "markAsServed": "Mark as served",
      "markAsBooked": "Mark as booked",
      "cancelBooking": "Cancel booking",
      "cancelAllBookings": "Cancel all bookings",
      "deleteAllSlots": "Delete slots",
      "deleteSlot": "Delete slot",
      "status": {
        "101": "Reserved",
        "201": "Booked",
        "301": "Cancelled",
        "401": "Served"
      },
      "plural": {
        "slot": "No slots | {n} slot | {n} slots"
      }
    }
  }
}
</i18n>

<template>
  <div class="slots-calendar column q-pa-md">
    <div class="q-gutter-y-md col q-mb-sm">
      <q-select
          v-if="showSelect"
          color="grey-7"
          v-model="service"
          :options="selectOptions"
          option-value="id"
          option-label="shortName"
          label="Услуга"
      >
        <template v-slot:prepend>
          <q-icon name="support_agent" />
        </template>
      </q-select>
      <div class="row justify-between">
        <div class="col row justify-start items-center"></div>
        <div class="row justify-center items-center">
          <q-btn color="grey-7" flat icon="keyboard_arrow_left" @click="prevWeek" />
          <q-btn color="grey-7" flat>
            {{ displayDate(dates[0]) }} &mdash; {{ displayDate(dates[6]) }}
            <q-popup-proxy ref="dateRangeProxy" transition-show="scale" transition-hide="scale">
              <q-date
                minimal
                mask="DD.MM.YYYY"
                :value="[{from: displayDate(dates[0]), to: displayDate(dates[6])}]"
                @input="setWeek"
                v-close-popup
              />
            </q-popup-proxy>
          </q-btn>
          <q-btn color="grey-7" flat icon="keyboard_arrow_right" @click="nextWeek" />
        </div>
        <div class="col row justify-end items-center">
          <q-btn
            v-if="!!service"
            dense
            icon="download"
            :title="$t('SlotsCalendar.downloadTickets')"
            @click="downloadSlots(organization.ogrn, serviceTargetId)"
          />
        </div>
      </div>
    </div>
    <div class="justify-center"></div>
    <div class="slots-calendar__container">
      <div class="slots-calendar__row slots-calendar__header row full-width justify-center">
        <div
          v-for="day in dates"
          :key="day.getTime()"
          :class="[
            'slots-calendar__day',
            'col-auto',
            'text-center',
            'q-py-sm',
            getDayColor(day),
          ]"
          :title="getDayDescription(day)"
        >
          <div class="text-weight-bold">{{ $t(`SlotsCalendar.daysOfWeek.${day.getDay()}`) }}</div>
          <div>{{ day.getDate() }}.{{ String(day.getMonth() + 1).padStart(2, '0') }}</div>

          <q-menu touch-position context-menu>
            <q-list dense style="min-width: 100px">
              <q-item
                clickable
                v-close-popup
                @click="cancelBooking(getTicketsOfDay(day))"
              >
                <q-item-section>{{
                  $t('SlotsCalendar.cancelAllBookings')
                }}</q-item-section>
              </q-item>
              <q-item
                clickable
                v-close-popup
                @click="deleteSlots(getSlotsOfDay(day))"
              >
                <q-item-section>{{
                  $t('SlotsCalendar.deleteAllSlots')
                }}</q-item-section>
              </q-item>
            </q-list>
          </q-menu>
        </div>
      </div>
      <div class="hour-grid-wrap">
        <div v-for="hour in gridHours" class="hour-grid" :key="hour">
          <span class="hour-grid__text">{{ hour }}:00</span>
        </div>
      </div>
      <div class="slots-calendar__row row full-width justify-center">
        <div
          v-for="(slotsCol, index) in calendarGrid"
          :key="`slots-col-${index}`"
          class="slots-calendar__day slots-calendar__day-times col-auto text-center bg-grey-1"
          :style="{ 'padding-top': `${slotsCol[0].padding}px` }"
        >
          <div
            v-for="slot in slotsCol"
            :key="slot.date + slot.hours + slot.minutes"
            :class="[
              'slots-calendar__time-slot',
              {
                'slots-calendar__time-slot--past': slot.isPast,
                'slots-calendar__time-slot--has-slots': slot.slots.length,
                'slots-calendar__time-slot--has-tickets': slot.tickets.length,
              }
            ]"
            :style="{ height: `${slot.height}px` }"
            :title="$tc('SlotsCalendar.plural.slot', slot.slots.length)"
            @click="selectTickets(slot)"
          >
            <div class="slots-calendar__time-text">
              {{ slot.hours }}:{{ slot.minutes }}
              <span class="slots-calendar__badeges-parking">
                <div class="slots-calendar__floating-badges">
                  <q-badge
                    v-for="(tickets, status) in slot.ticketsByStatus"
                    :key="status"
                    :color="getStatusColor(status)"
                    :title="$t(`SlotsCalendar.status.${status}`)"
                    v-show="tickets.length"
                    transparent
                  >
                    {{ tickets.length }}
                  </q-badge>
                </div>
              </span>
            </div>
            <q-menu v-if="!slot.isPast" touch-position context-menu>
              <q-list dense style="min-width: 100px">
                <q-item
                  v-if="slot.ticketsByStatus[201] && slot.ticketsByStatus[201].length"
                  clickable
                  v-close-popup
                  @click="cancelBooking(slot.ticketsByStatus[201])"
                >
                  <q-item-section>{{
                    $t('SlotsCalendar.cancelBooking')
                  }}</q-item-section>
                </q-item>
                <q-item
                  v-if="slot.slots.length"
                  clickable
                  v-close-popup
                  @click="deleteSlots(slot.slots)"
                >
                  <q-item-section>{{
                    $t('SlotsCalendar.deleteSlot')
                  }}</q-item-section>
                </q-item>
                <!-- <q-item
                  clickable
                  v-close-popup
                  @click="showSlots(slot)"
                >
                  <q-item-section>
                    Показать ID
                  </q-item-section>
                </q-item> -->
              </q-list>
            </q-menu>
          </div>
        </div>
      </div>
    </div>

    <q-dialog v-model="ticketsDialog" class="full-width">
      <q-card
        class="q-mb-md"
        :style="{
          maxWidth: '80vw',
          width: '1024px',
        }"
      >
        <q-card-section class="q-pb-none">
          <q-btn
            dense
            flat
            class="absolute-top-right q-ma-sm"
            color="grey-7"
            icon="close"
            size="10px"
            v-close-popup
          />
          <div class="text-h6">Посетители на <span v-if="selectedSlot">
            {{ selectedSlot.date }} {{ selectedSlot.hours }}:{{ selectedSlot.minutes }}
          </span></div>
        </q-card-section>
        <q-card-section>
          <q-table
            :data="selectedTickets"
            :columns="columns"
            :rows-per-page-options="[10, 20]"
          >
            <template v-slot:body-cell-fullName="props">
              <q-td
                :props="props"
                :title="props.value.status && props.value.status.statusMessage"
              >
                <q-item-label>
                  {{ props.value.lastName }}
                  {{ props.value.firstName }}
                  {{ props.value.middleName }}
                </q-item-label>
                <q-badge
                  :color="
                    getStatusColor(
                      props.value.status && props.value.status.statusCode
                    )
                  "
                >
                  {{ props.value.status && props.value.status.statusMessage }}
                </q-badge>
                <q-item-label caption>
                  {{
                    props.value.status.statusCode === '101'
                      ? `до ${getReservedUntil(props.value.reservedUntil)}`
                      : ''
                  }}
                  <div>{{ props.value.userSnils }}</div>
                </q-item-label>
              </q-td>
            </template>
            <template v-slot:body-cell-caseNumber="props">
              <q-td :props="props">
                <div v-if="applicationLoading">
                  <q-skeleton type="text" />
                  <q-skeleton type="text" />
                </div>
                <div v-else>
                  <q-item-label caption>
                    <span>{{ props.value.caseNumber }}</span>
                  </q-item-label>
                  <q-item-label
                    lines="2"
                    v-if="selectedStatements[props.value.caseNumber]"
                  >
                    {{
                      get(
                        selectedStatements[props.value.caseNumber],
                        'system.fullName'
                      )
                    }}
                  </q-item-label>
                  <q-item-label
                    class="text-red-5"
                    v-else-if="props.value.caseNumber && !applicationLoading"
                  >
                    Заявление с таким номером не найдено
                  </q-item-label>
                </div>
              </q-td>
            </template>
            <template v-slot:body-cell-actions="props">
              <q-td :props="props">
                <q-btn-group push>
                  <q-btn
                    v-if="ticketIs(props.value, 'booked')"
                    dense
                    flat
                    size="md"
                    icon="done_all"
                    title="Обслужить"
                    text-color="green" color="white"
                    @click="serveVisitor(props.value)"
                  />
                  <q-btn
                    v-if="props.value.caseNumber"
                    dense
                    flat
                    size="md"
                    icon="launch"
                    title="Открыть заявление"
                    text-color="primary" color="white"
                    :loading="applicationLoading"
                    :disable="!selectedStatements[props.value.caseNumber]"
                    @click="openStatement(props.value)"
                  />
                </q-btn-group>
              </q-td>
            </template>
          </q-table>
        </q-card-section>
      </q-card>
    </q-dialog>

  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
import { OrgScheduleDTO, SlotDTO, TicketDTO } from '@openapi/preorder-pgs';
import { convertDateFormat, isPastDate, getCurrentDate, } from '@utils/format-date';
import { DateFormat } from '@constants/utils/format-date';
import { convertDateFromBackendFormat } from '@/utils/format-date';
import preorderService from '@services/preorder';
import { Organization, Service } from '@openapi/registry-service';
import { shiftDateTo, getDaysInMs, parseDateTime, isDateValid, parseTimezone, timeParse, timeAdd, timeFormat } from '@components/tickets/utils/date-manipulations';
import { DocumentApi, Document } from '@openapi/document-registry';
import { notifyError, notifyInfo } from '@utils/notify';
import { WorkCalendarForDomain } from '../utils/WorkCalendarForDomain';
import { TableColumn } from '@/interfaces';
import { identity, isNil, isEmpty, some, get, isString } from 'lodash';

const documentApi = new DocumentApi();

interface Slot extends Omit<SlotDTO, 'visitTime' | 'endVisitTime'> {
  visitTime: string;
  endVisitTime: string;
}

interface Statement {
  fullName: string;
  shortName: string;
  startDate: string;
  applicant: {
    fullName: string;
  }
}

interface GridSlot {
  hours: string;
  minutes: string;
  slots: SlotDTO[];
  tickets: TicketDTO[];
  ticketsByStatus: Record<string, TicketDTO[]>;
  date: string;
  isPast: boolean;
  isWorkDay: boolean;
  height: number;
  padding: number;
}

interface ExtendedOrganization extends Organization {
  ogrn: string;
  regionId: string;
  services: Service[];
  parent: {
    timezone: string;
  };
}

enum TICKET_STATUS {
  'reserved' = '101',
  'booked' = '201',
  'cancelled' = '301',
  'served' = '401',
}

const TICKET_STATUS_COLORS: Record<string, string> = {
  '101': 'yellow-8',
  '201': 'green-6',
  '301': 'grey-6',
  '401': 'primary-6',
};

@Component({
  components: {},
  methods: {
    get,
  }
})
export default class SlotsCalendar extends Vue {
  // Импорт типа функции в класс
  get: typeof get;

  @Prop({default: () => ({})}) organization: ExtendedOrganization;
  @Prop({default: () => ''}) timezone: string;
  @Prop({default: () => ([])}) schedule: OrgScheduleDTO;
  @Prop({default: () => false}) showSelect: boolean;
  @Prop({default: () => ({})}) workCalendar: WorkCalendarForDomain;

  public selectedDate = convertDateFormat(new Date(), '', DateFormat.date);
  public service: Service | null = null;
  public slots: SlotDTO[] = [];
  public tickets: TicketDTO[] = [];

  public beginDate: Date = new Date();
  public endDate: Date = shiftDateTo(new Date(), 7);

  public selectOptions: Service[] = [];

  public selectedTickets: TicketDTO[] = [];
  public selectedSlot: GridSlot | null = null;
  public selectedStatements: Record<string, Document> = {};
  public ticketsDialog = false;

  public applicationLoading = false;

  @Watch('schedule', { immediate: true })
  private onScheduleChange(schedule: OrgScheduleDTO): void {
    if (isNil(this.organization?.services)) {
      return;
    }
    const index = schedule.services?.reduce((index, itm) => {
      if (itm.serviceId) {
        index[String(itm.serviceId)] = true
      }
      return index;
    }, ({} as Record<string, boolean>));
    if (!index) return;
    this.selectOptions = this.organization.services?.filter((itm) => {
      return index[itm.targetId];
    })
    if(isNil(this.selectOptions[0])) {
      return;
    }
    this.service = this.selectOptions[0];
    this.fetch(this.organization.ogrn, this.service.targetId, this.schedule?.areaId);
    this.fetchTickets(this.organization.ogrn, this.service.targetId, this.schedule?.areaId);
  }

  @Watch('service')
  onServiceChange(service?: Service): void {
    if (isNil(service)) {
      return;
    }
    this.fetch(this.organization.ogrn, service.targetId, this.schedule?.areaId);
    this.fetchTickets(this.organization.ogrn, service.targetId, this.schedule?.areaId);
  }

  public created(): void {
    this.calcDateRange(new Date(getCurrentDate()));
  }

  dates: Date[] = [];

  public calcDateRange(date: Date): void {
    const days = date.getDay() || 7;
    const begin = date.getTime() - getDaysInMs(days - 1);
    const end = date.getTime() + getDaysInMs(7 - days);
    this.beginDate = new Date(begin);
    this.endDate = new Date(end);

    const dates = [] as Date[];
    for (let day = 0; day < 7; day++) {
      dates.push(shiftDateTo(begin, day));
    }
    this.dates = dates;
  }

  getTimezone(): string {
    return parseTimezone(this.timezone);
  }

  public get slotsIndex(): Record<string, SlotDTO[]> {
    const index = {} as {[key: string]: SlotDTO[]}

    return this.slots.reduce((index, slot) => {
      
      const visitTime = parseDateTime(slot.visitTime || '');
      const endVisitTime = parseDateTime(slot.visitTime || '');

      const indexKey = slot.visitTime?.replace(/([-T:]|\+.*)/g, '') || '';
      if (!index[indexKey]) index[indexKey] = [];
      index[indexKey].push(slot);

      return index;
    }, index);
  }

  // private statrOfDay = timeParse('09:00:00');
  // private endOfDay = timeParse('24:00:00');

  get startOfWorkDay(): number {
    const arraySeconds =
      this.schedule?.schedules?.map((e) => {
        if (isString(e.startTime)) {
          return timeParse(e.startTime) || 0;
        }
        return 0;
      }) || [];
    return Math.min(...arraySeconds);
  }

  get endOfWorkDay(): number {
    const seconds =
      this.schedule?.schedules?.reduce((time: number, day) => {
        if (day.endTime) {
          return Math.max(time, timeParse(day.endTime));
        }
        return time;
      }, 0) || NaN;
    return seconds;
  }

  get serviceTargetId(): string | undefined {
    return this.service?.targetId;
  }

  public get ticketsIndex(): Record<string, TicketDTO[]> {
    const index = {} as {[key: string]: TicketDTO[]}
    return this.tickets.reduce((index, ticket) => {
      const indexKey = ticket.timeSlot?.visitTime?.replace(/([-T:]|\+.*)/g, '') || '';
      if (!index[indexKey]) index[indexKey] = [];
      index[indexKey].push(ticket);
      return index;
    }, index);
  }

  public get gridHours(): unknown[] {
    let numberOfSlots = (this.endOfWorkDay - this.startOfWorkDay) / 3600 || 0;
    numberOfSlots = Math.ceil(numberOfSlots);
    const firstHourSlots = Math.floor(this.startOfWorkDay / 3600) || 0;
    const numbers = [...Array(numberOfSlots).keys()].map(
      (i) => i + firstHourSlots
    );
    return numbers;
  }

  public get calendarGrid(): GridSlot[][] {
    const timeSlot = this.schedule.timeSlot || 0;
    const numberOfSlots =
      (this.endOfWorkDay - this.startOfWorkDay) / 60 / timeSlot;
    const shift = this.startOfWorkDay / 60;
    let time = 0;

    const grid = [] as GridSlot[][];

    for (let day = 0; day < 7; day++) {
      const date = this.dates[day];
      const DD = String(date.getDate()).padStart(2, '0');
      const YYYY = String(date.getFullYear()).padStart(2, '0');
      const MM = String(date.getMonth() + 1).padStart(2, '0');

      for (let i = 0; i < numberOfSlots; i++) {
        time = shift + timeSlot * i;
        const HH = String(Math.trunc(time / 60)).padStart(2, '0');
        const mm = String(time % 60).padStart(2, '0');
        const indexKey = `${YYYY}${MM}${DD}${HH}${mm}00`;

        const slots = this.slotsIndex[indexKey] || [];
        const tickets = this.ticketsIndex[indexKey] || [];
        const ticketsByStatus = SlotsCalendar.splitTickets(tickets);

        const isWorkDay = !!this.workCalendar.getDay(date).workDay;
        const isPast = isPastDate(new Date(`${YYYY}-${MM}-${DD} ${HH}:${mm}:00${this.getTimezone()}`));

        if (!grid[day]) grid[day] = [];
        grid[day].push({
          hours: HH,
          minutes: mm,
          date: `${DD}.${MM}.${YYYY}`,
          slots,
          tickets,
          ticketsByStatus,
          isPast,
          isWorkDay,
          height: timeSlot * 2,
          padding: i === 0 ? +mm * 2 : 0,
        });
      }
    }
    return grid;
  }

  getReservedUntil(date: string): string {
    if (!date) return '';
    return convertDateFromBackendFormat(date, DateFormat.date);
  }

  areSlotsBooked(slotIds: string[]): boolean {
    return some(this.tickets, ({ slotId, timeSlot, status }) => {
      slotId = slotId || timeSlot?.slotId;
      const booked = !!slotId && slotIds.includes(slotId) && status?.statusCode === '201';
      return booked;
    });
  }

  getDayIndex(date: Date): number {
    const day = date.getDay();
    if (day === 0) return 6;
    return day - 1;
  }

  getTicketsOfDay(date: Date): TicketDTO[] {
    return this.calendarGrid[this.getDayIndex(date)].flatMap(
      ({ tickets }) => tickets
    );
  }

  getSlotsOfDay(date: Date): SlotDTO[] {
    return this.calendarGrid[this.getDayIndex(date)].flatMap(
      ({ slots }) => slots
    );
  }

  getDayColor(date: Date): string {
    return this.workCalendar.getDay(date).workDay ? 'bg-gray-4' : 'bg-red-4';
  }

  getDayDescription(date: Date): string {
    return this.workCalendar.getDay(date).description || '';
  }

  private static splitTickets(tickets: TicketDTO[]): Record<string, TicketDTO[]> {
    return tickets.reduce((byStatuses, ticket) => {
      const index = ticket.status?.statusCode || 'other';
      if (!byStatuses[index]) byStatuses[index] = [];
      byStatuses[index].push(ticket);
      return byStatuses;
    }, {} as Record<string, TicketDTO[]>);
  }

  ticketStatus(status: keyof typeof TICKET_STATUS): string {
    return TICKET_STATUS[status];
  }

  ticketIs(ticket: TicketDTO, status: keyof typeof TICKET_STATUS): boolean {
    return ticket.status?.statusCode === TICKET_STATUS[status];
  }

  getStatusColor(statusCode?: string): string {
    if (!statusCode) return '';
    return TICKET_STATUS_COLORS[statusCode] || '';
  }

  async serveVisitor(ticket: TicketDTO): Promise<void> {
    const employeeId = this.$store.getters['oidcClient/currentUser']?.sub as string;
    try {
      await preorderService.finishTicket({
        bookId: ticket.bookId,
        employeeId,
        statusCode: 'served',
      });
      ticket.employeeId = employeeId;

      this.openStatement(ticket);
    } catch (error) {
      if (error instanceof Error) notifyError(error.message);
    }
  }

  public openStatement(ticket: TicketDTO): void {
    if (!ticket?.caseNumber) return;
    const applicationId = this.selectedStatements[ticket.caseNumber]?.id;
    if (applicationId) {
      this.$router.push({
        name: 'cards',
        params: {
          id: applicationId,
          path: 'statements',
        },
        query: {
          components: this.$store.getters['cardComponents/statements/components'],
          active: 'author',
        },
      });
    } else {
      notifyError(this.$t('SlotsCalendar.statementHasNoFound').toString());
    }
  }

  public async deleteSlots(slots: SlotDTO[]): Promise<void> {
    const slotIds = slots.flatMap(({ slotId }) => slotId || []);
    if (this.areSlotsBooked(slotIds)) {
      notifyError('Нельзя удалить занятые слоты');
      return;
    }
    await preorderService.deleteSlots(slotIds);

    this.fetch(
      this.organization.ogrn,
      this.service?.targetId,
      this.schedule?.areaId
    );
  }

  showSlots(slot: GridSlot): void {
    console.log(slot.slots.map(({ slotId }) => slotId));
  }

  confirmCancelBooking(tickets: TicketDTO[]): Promise<boolean> {
    return new Promise((resolve) => {
      this.$q
        .dialog({
          title: 'Отмена бронирования',
          message: `Действительно хотите отменить ${this.$tc(
            'SlotsCalendar.plural.slot',
            tickets.length
          )}`,
          cancel: true,
          persistent: true,
        })
        .onOk(() => {
          resolve(true);
        })
        .onCancel(() => {
          resolve(false);
        })
        .onDismiss(() => {
          resolve(false);
        });
    });
  }

  public async cancelBooking(tickets?: TicketDTO[]): Promise<void> {
    if (!tickets || isEmpty(tickets)) return;
    const descision = await this.confirmCancelBooking(tickets);
    if (!descision) return;
    const bookIds = tickets.flatMap(({ bookId }) => bookId || []);
    await preorderService.declineTickets(bookIds);

    this.fetch(
      this.organization.ogrn,
      this.service?.targetId,
      this.schedule?.areaId
    );
    this.fetchTickets(
      this.organization.ogrn,
      this.service?.targetId,
      this.schedule?.areaId
    );
  }

  public async markTicketAs(ticket: TicketDTO, statusName: keyof typeof TICKET_STATUS): Promise<void> {
    try {
      const statusCode = this.ticketStatus(statusName);
      const { status } = await preorderService.updateTicketStatus({
        bookId: ticket.bookId,
        statusCode,
      });
      ticket.status = status;
    } catch (error) {
      if (error instanceof Error) notifyError(error.message);
    }
  }

  public async setTicketEmployee(ticket: TicketDTO, employeeId: string): Promise<void> {
    try {
      const { status } = await preorderService.updateTicketEmployee({
        bookId: ticket.bookId,
        employeeId,
      });
      ticket.employeeId = employeeId;
    } catch (error) {
      if (error instanceof Error) notifyError(error.message);
    }
  }

  public async selectTickets(slot: GridSlot): Promise<void> {
    const tickets = slot.tickets;
    this.applicationLoading = true;
    if (isEmpty(tickets)) {
      return;
    }
    this.selectedSlot = slot;
    this.selectedTickets = tickets;
    const ids = tickets.map(({ caseNumber }) => `${caseNumber}`);

    this.ticketsDialog = true;
    try {
      const docs = await this.fetchApplications(ids);
      this.selectedStatements = docs;
    } catch (e) {
      console.group('Не удалось найти некоторые заявления');
      console.error(e);
      console.groupEnd();
      notifyInfo(`Не удалось найти некоторые заявления`);
    } finally {
      this.applicationLoading = false;
    }
  }

  public setWeek(val: any, reason: string, { day, month, year }: Record<string, string>): boolean {
    this.calcDateRange(new Date([year,month, day].join('/')));
    return false;
  }

  public prevWeek(): void {
    this.calcDateRange(shiftDateTo(this.dates[0], -7));
  }

  public nextWeek(): void {
    this.calcDateRange(shiftDateTo(this.dates[0], 7));
  }

  displayDate(date: Date): string {
    return convertDateFormat(
        date,
        '',
        DateFormat.date,
    );
  }

  async fetchApplicationId(orderId: string | undefined): Promise<string | undefined> {
    if (!orderId) return;
    const { data } = await documentApi.searchDocuments('Applications', `(common.orderId==${orderId})`);
    const document = data.find((document) => {
      return String(get(document, 'common.orderId')) === orderId;
    });
    return document?.id;
  }

  async fetchApplications(orderIds: string | string[] = ''): Promise<Record<string, Document>> {
    if (!Array.isArray(orderIds)) {
      orderIds = orderIds.split(',');
    }
    orderIds = orderIds.map((id) => `'${id}'`);
    const { data } = await documentApi.searchDocuments('Applications', `(common.orderId =in= (${orderIds.join(',')}))`);

    const results = data.reduce((results, document) => {
      results[String(get(document, 'common.orderId'))] = document;
      return results;
    }, {} as Record<string, Document>);
    return results;
  }

  async fetch(
    organizationId: string,
    serviceId?: string,
    areaId?: string
  ): Promise<void> {
    if (!serviceId) {
      notifyError('Невозможно получить слоты если не задана услуга');
      return;
    }
    try {
      this.slots = await preorderService.getSlots({
        organizationId,
        serviceId,
        areaId,
      });
    } catch (error) {
      this.slots = [];
    }
  }

  async fetchTickets(
    organizationId: string,
    serviceId?: string,
    areaId?: string
  ): Promise<void> {
    if (!serviceId) {
      notifyError('Невозможно получить бронирования если не задана услуга');
      return;
    }
    try {
      this.tickets = await preorderService.getTickets({
        organizationId,
        serviceId,
        areaId,
      });
    } catch (error) {
      this.tickets = [];
    }
  }

  async downloadSlots(organizationId: string, serviceId?: string): Promise<boolean> {
    try {
      return await preorderService.downloadSlots({ organizationId, serviceId });
    } catch (error) {
      notifyError(this.$t('SlotsCalendar.downloadError').toString());
      return false;
    }
  }

  columns: TableColumn<TicketDTO>[] = [
    {
      name: 'fullName',
      label: this.$t('SlotsCalendar.labels.fullName'),
      align: 'left',
      style: 'white-space: normal;',
      field: identity,
    },
    {
      name: 'caseNumber',
      label: this.$t('SlotsCalendar.labels.caseNumber'),
      align: 'left',
      style: 'white-space: normal;',
      field: identity,
      formatField: 'caseNumber',
    },
    {
      name: 'actions',
      label: this.$t('SlotsCalendar.labels.actions'),
      align: 'right',
      style: 'white-space: normal;',
      field: identity,
    },
  ];
};
</script>

<style lang="scss" scoped>
.slots-calendar {
  .hour-grid-wrap {
    width: 100%;
    position: absolute;
    .hour-grid {
      height: 1px;
      background: #c4c4c4;
      margin-bottom: 119px;
      &__text {
        font-size: 0.65rem;
        font-weight: 400;
        line-height: 1.25rem;
        color: #717171;
        letter-spacing: 0.03333em;
      }
      &:last-child {
        margin-bottom: 0;
      }
    }
  }

  &__container {
    position: relative;
    overflow: auto;
    // padding: 0 8px 0;
    // margin: 0 -8px 0;
  }

  &__header {
    position: sticky;
    top: 0;
    z-index: 0;
    user-select: none;
    background: #ffffff;
  }

  &__row {
    display: flex;
    flex-flow: row nowrap;
    justify-content: stretch;
    gap: 1em;
  }

  &__day {
    display: flex;
    flex-flow: column nowrap;
    flex: 1 1 14.258%;
    text-align: center;
    align-items: stretch;
    align-content: flex-start;
    justify-content: flex-start;
    background: #e0e0e0;
    &-times {
      opacity: 0.7;
    }

    &-header {
      display: inline-flex;
      flex-direction: column;
      align-items: stretch;
      position: relative;
      vertical-align: middle;
      font-size: 14px;
      line-height: 1.715em;
      text-decoration: none;
      color: inherit;
      font-weight: 500;
      text-transform: uppercase;
      text-align: center;
      width: auto;
      height: auto;
      flex: 0 1 auto;
      padding: 4px;
      border-radius: 4px 4px 0 0;
    }
  }

  &__time-text {
    position: relative;
    display: inline-block;
    padding-right: 1em;
    padding-left: 1em;
  }

  &__time-slot {
    position: relative;
    display: flex;
    flex-flow: row nowrap;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid #bcbcbc;
    color: #c6c6c6;
    user-select: none;

    cursor: pointer;

    &:first-child {
      border-top: 1px solid #EEEEEEFF;
    }

    &--past {
      background: #efefef;
    }

    &--has-tickets {
      &:hover {
        color: #5c7e9a;
      }
    }

    &--has-slots {
      font-weight: normal;
      color: black;
    }
  }

  &__badeges-parking {
    position: absolute;
    top: 0;
    right: 2px;
  }

  &__floating-badges {
    width: 46px;
    display: flex;
    flex-wrap: wrap;
    position: absolute;
    top: -4px;
    left: 0;
    cursor: inherit;
    gap: 2px;
  }

  &__time-string {
    display: block;
  }
}
</style>
