Mam napisaną aplikację w Vue.js. Wprowadź zmianę, aby pacjent był wyświetlany jako Nazwisko Imię zamiast jak dotychczas jako Imię Nazwisko. Ma to dotyczyć zarówno widoku tabeli (process-visit-view-row.vue) jak i kalendarza (process-visit-view-event.vue). Jednocześnie zmiana ta ma nie wpływać na sposób wyświetlania pacjenta w innych miejscach, gdzie użyty jest patient-title.

/// process-visit-view.vue

<template>
  <loading-container
    :is-loaded="isLoaded"
    fluid
    class="mx-0 py-0 px-1"
    style="max-height: calc(100vh - 120px); overflow-y: hidden">
    <v-overlay v-if="selectedVisit" z-index="30" :dark="false">
      <process-visit-card
        :value="selectedVisit"
        :tab.sync="selectedVisitTabId"
        :open-on-startup.sync="selectedVisitOpenOnStartup"
        @close="closeVisit"
        @submit="submitVisit" />
    </v-overlay>

    <v-overlay v-if="occupationalViewVisitId" z-index="30" :dark="false">
      <process-visit-occupational-docs
        :value="occupationalViewVisitId"
        @close="occupationalViewVisitId = null" />
    </v-overlay>

    <v-overlay v-if="patientHistoryVisit" z-index="30" :dark="false">
      <previous-visits-card :value="patientHistoryVisit" @close="patientHistoryVisit = null" />
    </v-overlay>
    <v-row dense class="my-1 mx-2" align="center">
      <v-col cols="auto">
        <v2-autocomplete
          v-if="hasAllStaffAccess"
          v-model="query.userId"
          :items="staffList"
          label="Specjalista"
          placeholder="Moje wizyty"
          clearable />
      </v-col>
      <v-spacer />
      <v-col cols="auto">
        <process-visit-summary-panel :staff-id="query.userId" :date="query.fromDate" />
      </v-col>
      <v-col cols="auto">
        <date-selector v-model="query.fromDate" dense :placeholder="today" />
      </v-col>
      <v-col cols="auto">
        <v-switch v-model="query.showClosed" label="Pokaż zrealizowane" hide-details class="my-0" />
        <v-switch v-model="query.hidePublic" label="Ukryj wizyty NFZ" hide-details class="my-0" />
      </v-col>
      <v-col cols="auto" class="ml-4">
        <tooltip-icon
          v-if="!isDefaultCalendarMode"
          icon="mdi-calendar-clock"
          tooltip="Przełącz na widok kalendarza"
          color="primary"
          clickable
          @click="toggleViewMode" />
        <tooltip-icon
          v-else
          icon="mdi-table-of-contents"
          tooltip="Przełącz na widok tabeli"
          color="primary"
          clickable
          @click="toggleViewMode" />
      </v-col>
    </v-row>
    <div style="min-height: 5px">
      <v-progress-linear v-if="isSearchLoading" indeterminate color="primary" />
    </div>

    <v-container
      class="ma-0 pa-0 px-1"
      style="max-height: calc(100vh - 160px); overflow-y: auto"
      fluid>
      <v-container
        v-for="(day, dayIdx) in searchResponse.days"
        :key="dayIdx"
        class="ma-0 pa-0"
        fluid>
        <v-toolbar class="day-header mb-2" dense dark color="cyan darken-1">
          <v-toolbar-title class="text-h6">
            {{ day.header }}
          </v-toolbar-title>
          <v-spacer />
          <tooltip-icon
            v-if="isDefaultCalendarMode && day.maxSimultaneousCount > maxSimultaneousCount"
            icon="mdi-calendar-remove"
            color="red darken-4"
            tooltip="Widok kalendarza niedostępny" />
        </v-toolbar>
        <v-calendar
          v-if="isDefaultCalendarMode && day.maxSimultaneousCount <= maxSimultaneousCount"
          ref="calendarRef"
          :value="day.calendarModel"
          class="pr-1"
          :events="day.eventsWithEmpty"
          :first-interval="day.firstInterval"
          event-overlap-mode="column"
          type="day"
          :interval-height="(60 / day.minDuration) * 48"
          :interval-minutes="60"
          hide-header
          :interval-count="day.intervalCount"
          :weekday-format="_ => null"
          :dark="false"
          :event-color="_ => 'transparent'">
          >
          <template #event="{ event }">
            <process-visit-view-event
              :value="event"
              :disabled="!event.entity?.canBeOpened ?? false">
              <template #actions>
                <v-row dense align="center">
                  <v-col cols="auto" align-self="center" class="mr-4">
                    <process-visit-delay-span :visit="event.entity" />
                  </v-col>
                  <v-col cols="auto" align-self="center">
                    <tooltip-icon
                      icon="mdi-history"
                      tooltip="Historia wizyt"
                      clickable
                      @click="patientHistoryVisit = event.entity" />
                  </v-col>
                  <v-col cols="auto" align-self="center">
                    <submit-icon-button
                      :action="() => notifyPatient(event.entity)"
                      :disabled="!event.entity.canBeOpened"
                      :tooltip="
                        event.entity.canBeOpened
                          ? 'Powiadom pacjenta'
                          : event.entity.openDisableReason
                      "
                      icon="mdi-speaker-wireless"
                      color="primary darken-3" />
                  </v-col>
                  <v-col cols="auto" align-self="center">
                    <tooltip-icon
                      icon="mdi-lock-open-outline"
                      :disabled="!event.entity.canBeOpened"
                      :tooltip="
                        event.entity.canBeOpened ? 'Otwórz wizytę' : event.entity.openDisableReason
                      "
                      clickable
                      @click="openVisit(event.entity)" />
                  </v-col>
                </v-row>
              </template>
            </process-visit-view-event>
          </template>
        </v-calendar>
        <template v-for="(visit, idx) in day.visits" v-else>
          <process-visit-view-row :key="idx" :value="visit">
            <template #actions>
              <v-row dense align="center">
                <v-col cols="auto" align-self="center" class="mr-4">
                  <process-visit-delay-span :visit="visit" />
                </v-col>
                <v-col cols="auto" align-self="center">
                  <tooltip-icon
                    icon="mdi-history"
                    tooltip="Historia wizyt"
                    clickable
                    @click="patientHistoryVisit = visit" />
                </v-col>
                <v-col cols="auto" align-self="center">
                  <submit-icon-button
                    :action="() => notifyPatient(visit)"
                    :disabled="!visit.canBeOpened"
                    :tooltip="visit.canBeOpened ? 'Powiadom pacjenta' : visit.openDisableReason"
                    icon="mdi-speaker-wireless"
                    color="primary darken-3" />
                </v-col>
                <v-col cols="auto" align-self="center">
                  <tooltip-icon
                    icon="mdi-lock-open-outline"
                    :disabled="!visit.canBeOpened"
                    :tooltip="visit.canBeOpened ? 'Otwórz wizytę' : visit.openDisableReason"
                    clickable
                    @click="openVisit(visit)" />
                </v-col>
                <v-col
                  v-if="visit.isMasterOccupationalVisit && visit.patient.id"
                  cols="auto"
                  align-self="center">
                  <tooltip-icon
                    icon="mdi-file-document-multiple-outline"
                    tooltip="Dokumenty medycyny pracy"
                    clickable
                    @click="occupationalViewVisitId = visit.id" />
                </v-col>
              </v-row>
            </template>
          </process-visit-view-row>

          <v-divider v-if="idx < day.visits.length" :key="idx" class="my-2" />
        </template>
      </v-container>

      <infinite-loading ref="infiniteLoaderRef" @infinite="infiniteHandler">
        <span slot="no-more" class="text-caption">Wszystkie wizyty wyświetlone</span>
        <span slot="no-results">
          <v-container fluid class="ma-0 px-0">
            <v-card v-if="searchResponse.isEmpty" elevation="1">
              <v-card-text>Brak wizyt spełniających kryteria</v-card-text>
            </v-card>
          </v-container>
        </span>
        <span slot="spinner">
          <list-loading />
        </span>
      </infinite-loading>
    </v-container>
  </loading-container>
</template>
<script lang="ts">
import {
  computed,
  defineComponent,
  inject,
  onBeforeUpdate,
  onMounted,
  onUnmounted,
  ref,
  watch
} from "@vue/composition-api";
import axios from "@/lib/axios";
import LoadingContainer from "@/components/loading/loading-container.vue";
import MedicaRouter from "@/views/main-wrap/medica-router";
import AppState from "@/views/main-wrap/app-state";
import StateMappings from "@/views/main-wrap/state-mappings";
import { StaffVisitsQuery } from "@/views/commerce/visits/process/staff-visits-query";
import DateSelector from "@/components/date-time/date-selector.vue";
import Util from "@/lib/util";
import { VisitsSearchResponse } from "@/views/commerce/visits/visits-search-response";
import TooltipIcon from "@/components/simple/tooltip-icon.vue";
import { addDays, format } from "date-fns";
import InfiniteLoading from "vue-infinite-loading";
import ProcessVisitCard from "@/views/commerce/visits/process/process-visit-card.vue";
import { Visit } from "@/views/commerce/visits/visit";
import BaseComponent from "@/lib/base-component-utils";
import { PermissionNames } from "@/lib/permissions";
import PatientVisitStatus from "@/views/commerce/visits/browse/patient-visit-status";
import PreviousVisitsCard from "@/views/commerce/visits/process/previous-visits/previous-visits-card.vue";
import debounce from "lodash/debounce";
import SubmitIconButton from "@/components/action-buttons/submit-icon-button.vue";
import ProcessVisitViewRow from "@/views/commerce/visits/process/process-visit-view-row.vue";
import ProcessVisitOccupationalDocs from "@/views/commerce/visits/process/process-visit-occupational-docs.vue";
import ProcessVisitSummaryPanel from "@/views/commerce/visits/process/process-visit-summary-panel.vue";
import ProcessVisitDelaySpan from "@/views/commerce/visits/process/process-visit-delay-span.vue";
import V2Autocomplete from "@/compatibility-layer/components/v2-autocomplete.vue";
import ProcessVisitViewEvent from "@/views/commerce/visits/process/process-visit-view-event.vue";

const ProcessVisitView = defineComponent({
  components: {
    ProcessVisitViewEvent,
    V2Autocomplete,
    ProcessVisitDelaySpan,
    ProcessVisitSummaryPanel,
    ProcessVisitOccupationalDocs,
    PreviousVisitsCard,
    ProcessVisitCard,
    SubmitIconButton,
    TooltipIcon,
    ProcessVisitViewRow,
    DateSelector,
    LoadingContainer,
    InfiniteLoading
  },

  props: {},

  emits: ["edit", "delete"],

  setup(props, context) {
    const router = ref(MedicaRouter.forContext(context));
    const pageTitleDefault = ref("" as string);
    const appState = inject(AppState.Key) as AppState;
    const stateMappings = ref(StateMappings.create(appState));
    const isLoaded = ref(false);
    const isSearchLoading = ref(false);
    const query = ref(new StaffVisitsQuery());
    const searchResponse = ref(VisitsSearchResponse.empty());
    const infiniteLoaderRef = ref(null);
    const selectedVisitId = ref(null as number | null);
    const selectedVisit = ref(null as Visit | null);
    const selectedVisitOpenOnStartup = ref(false);
    const selectedVisitTabId = ref(null as number | null);
    const isDefaultCalendarMode = ref(false);

    const hasAllStaffAccess = computed(
      () =>
        BaseComponent.hasPermission(PermissionNames.PatientsMedicalDocumentation_Modify) ||
        BaseComponent.hasPermission(PermissionNames.PatientsMedicalDocumentation_View)
    );

    const userId = computed(() => {
      if (hasAllStaffAccess.value && query.value.userId) return query.value.userId;
      return appState.currentUserData.userId;
    });

    const lastEventName = ref(null as string | null);
    const eventName = computed(() => `staff_${userId.value}_visits_update`);
    onBeforeUpdate(() => {
      infiniteLoaderRef.value = null;
    });

    onMounted(async () => {
      pageTitleDefault.value = Util.getTitlePagePart();

      router.value.getParams(query.value);
      selectedVisitId.value = router.value.getParam("id", null);
      selectedVisitTabId.value = router.value.getParam("tabId", null);
      selectedVisitOpenOnStartup.value = router.value.getParam("openOnStartup", false);

      if (selectedVisitId.value) {
        const response = await axios.get("/api/services/app/Visits/GetShallow", {
          params: { id: selectedVisitId.value }
        });

        selectedVisit.value = Visit.fromDto(response.data.result);

        Util.title(selectedVisit.value.patient.name || pageTitleDefault.value);
      }
      await loadData();
      await updateSubscription();
    });

    const updateSubscription = async () => {
      const newEventNameVal = eventName.value;
      const lastEventNameVal = lastEventName.value;
      if (newEventNameVal !== lastEventNameVal) {
        if (lastEventNameVal) {
          await appState.eventUnsubscribe(lastEventNameVal, updateDay, false);
        }
        lastEventName.value = newEventNameVal;
        await appState.eventSubscribe(newEventNameVal, updateDay, false);
      }
    };

    watch(userId, (_1, _2) => updateSubscription());

    onUnmounted(async () => {
      await unsubscribe();
    });

    const unsubscribe = async () => {
      const lastEventNameVal = lastEventName.value;
      if (lastEventNameVal) await appState.eventUnsubscribe(lastEventNameVal, updateDay, false);
    };

    const loadData = async () => {
      await loadMore();
      isLoaded.value = true;
    };

    const reload = async () => {
      isSearchLoading.value = true;
      searchResponse.value = VisitsSearchResponse.empty();
      const refVal = infiniteLoaderRef.value as any;
      if (refVal) {
        refVal.stateChanger.reset();
      }
    };

    const reloadDebounced = debounce(() => {
      isSearchLoading.value = true;
      reload();
    }, 500);

    const updateDay = (day: string) => {
      setTimeout(() => {
        isSearchLoading.value = true;
        const dto = query.value.dto;
        dto.fromDate = day;
        dto.maxResultCount = null;
        const response = axios
          .post("/api/services/app/VisitsSearch/StaffVisitsSearch", dto)
          .then(response => {
            searchResponse.value.updateDays(response.data.result);
            isSearchLoading.value = false;
          });
      }, 1000);
    };

    const setRouterParams = async (q: StaffVisitsQuery) => {
      const dto = selectedVisitId.value
        ? {
            ...q.serializableQuery,
            id: selectedVisitId.value,
            tabId: selectedVisitTabId.value,
            openOnStartup: selectedVisitOpenOnStartup.value
          }
        : q.serializableQuery;
      await router.value.setParams(
        dto,
        selectedVisit.value?.patient.name || pageTitleDefault.value
      );
    };

    watch(
      query,
      async (nv, _) => {
        if (isLoaded.value && nv) {
          await setRouterParams(nv as StaffVisitsQuery);
          reloadDebounced();
        }
      },
      {
        deep: true
      }
    );

    const infiniteHandler = ($state: any) => {
      loadMore().then(isMore => {
        if (isMore) {
          $state.loaded();
        } else {
          $state.complete();
        }
      });
    };

    const loadMore = async (): Promise<boolean> => {
      isSearchLoading.value = true;
      try {
        const dto = query.value.dto;
        const daysCount = searchResponse.value.days.length;

        if (daysCount) {
          dto.fromDate = format(
            addDays(new Date(searchResponse.value.days[daysCount - 1].date), 1),
            "yyyy-MM-dd"
          );
        }
        const response = await axios.post("/api/services/app/VisitsSearch/StaffVisitsSearch", dto);
        const isMore = !!response.data.result.visits.length;
        searchResponse.value.loadDto(response.data.result, true);
        return isMore;
      } finally {
        isSearchLoading.value = false;
      }
    };

    const patientHistoryVisit = ref(null as Visit | null);
    const openVisit = async (visit: Visit) => {
      patientHistoryVisit.value = null;
      selectedVisitId.value = visit.id;
      selectedVisit.value = visit;
      selectedVisitOpenOnStartup.value = visit.status === PatientVisitStatus.Pending.id;
      await setRouterParams(query.value as StaffVisitsQuery);
    };

    const notifyPatient = async (visit: Visit) => {
      await new Promise(resolve => setTimeout(resolve, 5000));
      await axios.post("/api/services/app/Notifications/NotifyPatient", {
        officeId: visit.office.id,
        visitId: visit.id
      });
      await new Promise(resolve => setTimeout(resolve, 5000));
    };

    watch(selectedVisitTabId, async (nv, prev) => {
      if (nv !== prev) await setRouterParams(query.value as StaffVisitsQuery);
    });

    watch(selectedVisitOpenOnStartup, async (nv, prev) => {
      if (nv !== prev) await setRouterParams(query.value as StaffVisitsQuery);
    });

    const closeVisit = async () => {
      selectedVisitId.value = null;
      selectedVisit.value = null;
      selectedVisitOpenOnStartup.value = false;
      selectedVisitTabId.value = null;
      await setRouterParams(query.value as StaffVisitsQuery);
    };

    const submitVisit = async () => {
      await closeVisit();
      reloadDebounced();
    };

    const staffList = computed(() => appState.staff.filter(v => v.isAssigned));

    const occupationalViewVisitId = ref<number | null>(null);

    const today = computed(() => Util.formatToday());

    const toggleViewMode = () => {
      isDefaultCalendarMode.value = !isDefaultCalendarMode.value;
    };

    return {
      isLoaded,
      isSearchLoading,
      query,
      searchResponse,
      infiniteHandler,
      infiniteLoaderRef,
      selectedVisitId,
      selectedVisit,
      openVisit,
      notifyPatient,
      closeVisit,
      submitVisit,
      hasAllStaffAccess,
      selectedVisitOpenOnStartup,
      staffList,
      selectedVisitTabId,
      stateMappings,
      patientHistoryVisit,
      occupationalViewVisitId,
      today,
      isDefaultCalendarMode,
      toggleViewMode,
      maxSimultaneousCount: 2
    };
  }
});

export default ProcessVisitView;
</script>
<style scoped>
.day-header {
  position: sticky;
  top: 0;
  z-index: 1;
}
</style>

/// process-visit-view-row.vue

<template>
  <v-row
    dense
    align="center"
    :style="{
      'border-left': `5px solid ${value.appointmentTypeColor ?? 'transparent'}`,
      'border-radius': '5px'
    }">
    <v-col cols="4" lg="3">
      <patient-title small :visit="value" with-age class="pl-2">
        <template #append>
          <v-row dense>
            <v-col v-if="value.isTechnical" cols="auto" class="py-0">
              <tooltip-icon small icon="mdi-border-none" tooltip="Wizyta techniczna" />
            </v-col>
            <v-col cols="auto" class="py-0">
              <payment-mode-icons :value="value.paymentModeSingleSet" />
            </v-col>
          </v-row>
        </template>
      </patient-title>
    </v-col>

    <v-col cols="3" lg="4">
      <text-caption small icon="mdi-stethoscope" class="pl-1">
        {{ stateMappings.specializationName(value.specializationId) }}
        <template #caption>
          <div>{{ value.appointmentTypeName }}</div>
          <div v-if="aggregatedServices" class="primary--text text--darken-2">
            {{ aggregatedServices }}
          </div>
        </template>
      </text-caption>
      <div>
        <text-suffix-caption
          small
          :caption="value.office.shortFormatted"
          icon="custom"
          justify="start">
          {{ stateMappings.orgUnitName(value.office.organizationUnitId) }}
          <template #icon>
            <visit-appointment-mode-icon :value="value.visitAppointmentMode" />
          </template>
        </text-suffix-caption>
      </div>
    </v-col>

    <v-col cols="5">
      <v-row dense align="center">
        <v-col>
          <span :class="value.statusObj.textColor">
            <visit-status-icon :show-tooltip="false" :value="value.status" />
            {{ value.statusObj.name }}
          </span>
        </v-col>
        <v-col cols="auto">
          <slot name="actions"></slot>
        </v-col>

        <v-col cols="auto" align-self="center">
          <text-caption justify="end" hide-caption class="pr-4">
            {{ value.timeRangeFormatted }}
          </text-caption>
        </v-col>
      </v-row>
      <div class="text-caption pl-2 pb-1">
        <visit-check-in-summary-span :value="value.checkInData" />
        <div v-if="value.surveyStatus.name">
          Ankieta:
          <strong>{{ value.surveyStatus.name }}</strong>
        </div>
      </div>

      <div v-if="value.descriptionRaw" class="mt-n2 pl-2">
        <v-icon small left>mdi-information-outline</v-icon>
        <span class="text-caption" v-text="value.descriptionRaw"></span>
      </div>
    </v-col>
  </v-row>
</template>
<script lang="ts">
import type { PropType } from "@vue/composition-api";
import { computed, defineComponent, inject, ref } from "@vue/composition-api";
import AppState from "@/views/main-wrap/app-state";
import StateMappings from "@/views/main-wrap/state-mappings";
import type { Visit } from "@/views/commerce/visits/visit";
import PatientTitle from "@/views/patients/visit-patient/patient-title.vue";
import TooltipIcon from "@/components/simple/tooltip-icon.vue";
import PaymentModeIcons from "@/views/commerce/visits/payment-mode-icons.vue";
import TextCaption from "@/components/simple/text-caption.vue";
import TextSuffixCaption from "@/components/simple/text-suffix-caption.vue";
import VisitAppointmentModeIcon from "@/views/commerce/visits/visit-appointment-mode-icon.vue";
import VisitStatusIcon from "@/views/commerce/visits/visit-status-icon.vue";
import SubmitIconButton from "@/components/action-buttons/submit-icon-button.vue";
import VisitCheckInSummarySpan from "@/views/commerce/visits/visit-card/check-in-data/visit-check-in-summary-span.vue";

const ProcessVisitViewRow = defineComponent({
  components: {
    VisitCheckInSummarySpan,
    SubmitIconButton,
    VisitStatusIcon,
    VisitAppointmentModeIcon,
    TextSuffixCaption,
    TextCaption,
    PaymentModeIcons,
    TooltipIcon,
    PatientTitle
  },

  props: {
    value: {
      type: Object as PropType<Visit>,
      required: true
    }
  },

  setup(props, context) {
    const appState = inject(AppState.Key) as AppState;
    const stateMappings = ref(StateMappings.create(appState));

    const aggregatedServices = computed(() =>
      props.value.aggregatedServices
        .map(v => stateMappings.value.medicalServiceName(v.id!))
        .join(", ")
    );

    return {
      aggregatedServices,
      stateMappings
    };
  }
});

export default ProcessVisitViewRow;
</script>


/// process-visit-view-event.vue

<template>
  <v-card
    :color="entity ? '#f1f1f1' : '#ffffff'"
    flat
    elevation="4"
    :ripple="!disabled"
    :hover="!disabled"
    style="border: 1px solid #999999"
    height="100%"
    :style="{
      'border-left': `8px solid ${entity?.appointmentTypeColor ?? 'transparent'}`,
      'border-radius': '8px 4px 4px 8px'
    }"
    class="px-2 overflow-y-auto process-event">
    <v-row dense align="center">
      <v-col cols="auto">
        <text-caption justify="end" :caption="'( ' + value.durationInMinutes + ' min )'">
          {{ value.timeRangeFormatted }}
        </text-caption>
      </v-col>

      <template v-if="entity">
        <v-col cols="3">
          <v-row dense>
            <v-col>
              <patient-title small :visit="entity">
                <template #caption><span></span></template>
              </patient-title>
            </v-col>
          </v-row>
          <v-row dense no-gutters>
            <v-col cols="auto" class="py-0">
              <span class="text-caption">
                {{ entity.patient.ageFormatted }}
              </span>
            </v-col>
            <v-col cols="auto" class="pl-1 py-0">
              <payment-mode-icons :value="entity.paymentModeSingleSet" />
            </v-col>
            <v-col v-if="entity.isTechnical" cols="auto" class="pl-1 py-0">
              <tooltip-icon small icon="mdi-border-none" tooltip="Wizyta techniczna" />
            </v-col>
          </v-row>
        </v-col>

        <v-col>
          <text-caption icon-size="18" icon="mdi-stethoscope" class="pl-1 d-inline-block">
            <template #caption>
              <div>{{ entity.appointmentTypeName }}</div>
              <div v-if="aggregatedServices" class="primary--text text--darken-2">
                {{ aggregatedServices }}
              </div>
            </template>
            <div class="d-inline-block">
              {{ stateMappings.specializationName(entity.specializationId) }}
            </div>
          </text-caption>
        </v-col>

        <v-col cols="auto">
          <visit-appointment-mode-icon
            :value="entity.visitAppointmentMode"
            :info="
              stateMappings.orgUnitName(entity.office.organizationUnitId) +
              ' (' +
              entity.office.shortFormatted +
              ')'
            " />
        </v-col>
        <v-col cols="auto">
          <span :class="entity.statusObj.textColor">
            <visit-status-icon :show-tooltip="false" :value="entity.status" />
          </span>
        </v-col>
        <v-col cols="auto">
          <slot name="actions"></slot>
        </v-col>
      </template>
    </v-row>
  </v-card>
</template>
<script lang="ts">
import type { PropType } from "@vue/composition-api";
import { computed, defineComponent, inject, ref } from "@vue/composition-api";
import AppState from "@/views/main-wrap/app-state";
import StateMappings from "@/views/main-wrap/state-mappings";
import type { Visit } from "@/views/commerce/visits/visit";
import PatientTitle from "@/views/patients/visit-patient/patient-title.vue";
import TooltipIcon from "@/components/simple/tooltip-icon.vue";
import PaymentModeIcons from "@/views/commerce/visits/payment-mode-icons.vue";
import TextCaption from "@/components/simple/text-caption.vue";
import TextSuffixCaption from "@/components/simple/text-suffix-caption.vue";
import VisitAppointmentModeIcon from "@/views/commerce/visits/visit-appointment-mode-icon.vue";
import VisitStatusIcon from "@/views/commerce/visits/visit-status-icon.vue";
import SubmitIconButton from "@/components/action-buttons/submit-icon-button.vue";
import VisitCheckInSummarySpan from "@/views/commerce/visits/visit-card/check-in-data/visit-check-in-summary-span.vue";
import type { VisitEvent } from "@/views/commerce/visits/visits-search-hit";

const ProcessVisitViewRow = defineComponent({
  components: {
    VisitCheckInSummarySpan,
    SubmitIconButton,
    VisitStatusIcon,
    VisitAppointmentModeIcon,
    TextSuffixCaption,
    TextCaption,
    PaymentModeIcons,
    TooltipIcon,
    PatientTitle
  },

  props: {
    value: {
      type: Object as PropType<VisitEvent>,
      required: true
    },
    disabled: {
      type: Boolean,
      required: false,
      default: false
    }
  },

  setup(props, context) {
    const appState = inject(AppState.Key) as AppState;
    const stateMappings = ref(StateMappings.create(appState));

    const aggregatedServices = computed(() =>
      (props.value.entity as Visit).aggregatedServices
        .map(v => stateMappings.value.medicalServiceName(v.id!))
        .join(", ")
    );

    return {
      aggregatedServices,
      stateMappings,
      entity: props.value.entity as Visit | null
    };
  }
});

export default ProcessVisitViewRow;
</script>
<style>
.process-event .text-body-2 {
  font-size: 12px !important;
}
</style>


/// patient-title.vue

<template>
  <text-caption :dark="dark" :class="{ narrow: isSmall }" :hide-icon="isSmall" :icon="true">
    <v-row dense align="center">
      <v-col cols="auto">
        <span :class="isSmall ? 'text--bold' : 'text-h6'">
          {{ visit.patient.name }}
        </span>
        <span v-if="withAge" class="ml-1 text-caption">{{ visit.patient.ageFormatted }}</span>
      </v-col>
      <v-col cols="auto">
        <slot name="append"></slot>
      </v-col>
      <v-col v-if="!isSmall" cols="auto">
        <send-patient-message-dialog :visit="visit">
          <template #default="{ show }">
            <tooltip-icon
              class="ml-2 text-h6"
              clickable
              icon="mdi-message-text-outline"
              :color="iconColor"
              tooltip="Wyślij SMS"
              @click="show" />
          </template>
        </send-patient-message-dialog>
        <tooltip-icon
          class="ml-2 text-h6"
          clickable
          :color="iconColor"
          icon="mdi-phone"
          tooltip="Zadzwoń (CallManager)"
          @click="openCallManager" />
      </v-col>
    </v-row>

    <template #icon>
      <slot name="icon">
        <confirmation-icon :value="visit.confirmation" />
      </slot>
    </template>
    <template #caption>
      <slot name="caption">
        <patient-title-caption :value="visit.patient" :small="isSmall" :dark="dark" />
      </slot>
    </template>
  </text-caption>
</template>
<script lang="ts">
import type { PropType } from "@vue/composition-api";
import { computed, defineComponent, inject } from "@vue/composition-api";
import TooltipIcon from "@/components/simple/tooltip-icon.vue";
import TextCaption from "@/components/simple/text-caption.vue";
import type { Visit } from "@/views/commerce/visits/visit";
import ConfirmationIcon from "@/views/commerce/visits/confirmation-icon.vue";
import View from "@/views/main-wrap/view";
import SendPatientMessageDialog from "@/views/patients/patients-directory/send-patient-message-dialog.vue";
import CallManager from "@/services/call-manager/call-manager";
import PatientTitleCaption from "@/components/patients/patient-title-caption.vue";

const PatientTitle = defineComponent({
  components: {
    PatientTitleCaption,
    SendPatientMessageDialog,
    ConfirmationIcon,
    TextCaption,
    TooltipIcon
  },
  props: {
    visit: {
      type: Object as PropType<Visit>,
      required: true
    },
    small: {
      type: Boolean,
      required: false,
      default: false
    },
    flexSize: {
      type: Boolean,
      required: false,
      default: false
    },
    dark: {
      type: Boolean,
      required: false,
      default: false
    },
    withAge: {
      type: Boolean,
      required: false,
      default: false
    }
  },

  setup(props, context) {
    const view = new View(context);
    const callManager = inject(CallManager.Key) as CallManager;

    const isSmall = computed(() => props.small || (props.flexSize && !view.xlOnly));

    const openCallManager = async () => {
      if (props.visit.isReschedule) {
        await callManager.openReschedule(props.visit.id!);
      } else if (props.visit.confirmation && props.visit.confirmation.canConfirm) {
        await callManager.openConfirmation(props.visit.id!);
      } else {
        await callManager.openVisit(props.visit.id!);
      }
    };

    const iconColor = computed(() => (props.dark ? "primary lighten-2" : "primary darken-2"));

    return { view, isSmall, openCallManager, iconColor };
  }
});

export default PatientTitle;
</script>
<style scoped>
.narrow {
  max-width: 320px;
}
</style>