Masz aplikację napisaną w vue.js. W plikach dotyczących printer dodaj funkcjonalności związane z CRUD. Wzoruj się przy tym na plikach związanych z occupational-factor. Nie usuwaj i nie zmieniaj ptzy tym funkcjonalności istniejących obecnie w printer. Wygeneruj pełną treść plików, które powinny być zmodyfikowane lub utworzone w celu dodania nowych funkcjonalności.

/// printer.ts

import { GenericEntity } from "@/views/commerce/common/named-item";
import PrinterStatus from "@/views/commerce/printers/printer-status";
import PrinterTarget from "@/views/commerce/printers/printer-target";
import type { SelectPanelOption } from "@/components/action-buttons/select-panel-option";

export default class Printer extends GenericEntity implements SelectPanelOption {
  id: string;
  orgUnitId: number;
  name: string | null;
  target: PrinterTarget = PrinterTarget.instance.reception;
  status: PrinterStatus = PrinterStatus.empty();

  public static fromDto(dto: any): Printer {
    const entity = new Printer();
    entity.readDto(dto);
    return entity;
  }

  protected readDto(dto: any) {
    super.readDto(dto);
    this.id = dto.id;
    this.orgUnitId = dto.orgUnitId;
    this.name = dto.name;
    this.target = PrinterTarget.getById(dto.target);
    this.status = dto.status ? PrinterStatus.fromDto(dto.status) : PrinterStatus.empty();
  }

  get displayName() {
    if (this.name) return `${this.name} (${this.id})`;
    return this.id;
  }

  get dto(): any {
    return {
      id: this.id
    };
  }
}

/// printers-query.ts

import { PagedTextQuery } from "@/views/commerce/common/paged-text-query";

export class PrintersQuery extends PagedTextQuery {
  orgUnitId: number | null = null;

  get serializableQuery(): any {
    return this.nonDefaultValues(new PrintersQuery());
  }

  get dto(): any {
    return {
      ...super.dto,
      orgUnitId: this.orgUnitId
    };
  }
}

/// printers-view-row.vue

<template>
  <v-row align="center">
    <v-col>
      <v-row dense>
        <v-col cols="auto">
          <span :class="{ 'text-body-2': true, 'text--bold': true }" v-text="value.displayName"></span>
        </v-col>
        <v-col cols="auto">
          <span
            :class="{
              'text-body-2': true
            }">
            <v-icon small class="mr-1">mdi-map-marker</v-icon>
            {{ stateMappings.orgUnitName(value.orgUnitId) }}
          </span>
        </v-col>
      </v-row>
    </v-col>
    <v-col cols="auto">
      <span v-if="value.status.serialNumber" class="text-body-2 primary--text text--darken-2">
        {{ value.status.serialNumber }}
      </span>
    </v-col>
    <v-col cols="auto">
      <div class="text-body-2" style="min-width: 100px" v-text="value.target.name"></div>
    </v-col>
    <v-col cols="auto">
      <tooltip-icon
        :icon="icon"
        :color="color"
        :tooltip="value.status.error"
        :show-tooltip="value.status.error" />
    </v-col>
    <v-col cols="auto">
      <v2-btn color="primary" small @click="select">
        <v-icon>mdi-eye</v-icon>
      </v2-btn>
    </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 Printer from "@/views/commerce/printers/printer";
import V2Btn from "@/compatibility-layer/components/v2-btn.vue";
import TooltipIcon from "@/components/simple/tooltip-icon.vue";

const TerminalsView = defineComponent({
  components: {
    TooltipIcon,
    V2Btn
  },

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

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

    const icon = computed(() => {
      if (props.value.status.isUnknown) return "mdi-map-marker-question-outline";
      if (props.value.status.isNoConnection) return "mdi-close-circle";
      if (props.value.status.error) return "mdi-alert";
      return "mdi-check";
    });

    const color = computed(() => {
      if (props.value.status.isUnknown) return "primary darken-2";
      if (props.value.status.isNoConnection) return "error";
      if (props.value.status.error) return "orange";
      return "success";
    });

    return {
      stateMappings,
      icon,
      color,
      select: () => context.emit("select")
    };
  }
});

export default TerminalsView;
</script>

/// printers-view.vue

<template>
  <loading-container :is-loaded="isLoaded" fluid class="ma-0 pa-0">
    <v-overlay v-if="selectedItem" z-index="20" :dark="false">
      <printer-status-card :value="selectedItem" @close="onCardClose" />
    </v-overlay>
    <v-row dense align="center" justify="space-between">
      <v-col cols="4">
        <organization-unit-selector v-model="query.orgUnitId" />
      </v-col>
      <v-col cols="auto">
        <submit-button :action="refresh">
          Odśwież
        </submit-button>
      </v-col>
    </v-row>
    <loading-bar :value="isSearchLoading" />

    <template v-for="(item, idx) in items">
      <printers-view-row :key="idx" :value="item" @select="() => (selectedItem = item)" />

      <v-divider v-if="idx < items.length - 1" :key="idx" class="my-2" />
    </template>
  </loading-container>
</template>
<script lang="ts">
import { computed, defineComponent, inject, onMounted, ref, watch } from "@vue/composition-api";
import axios from "@/lib/axios";
import debounce from "lodash/debounce";
import MedicaRouter from "@/views/main-wrap/medica-router";
import AppState from "@/views/main-wrap/app-state";
import type { Terminal } from "@/views/commerce/terminals/terminal";
import LoadingContainer from "@/components/loading/loading-container.vue";
import StateMappings from "@/views/main-wrap/state-mappings";
import PrinterStatusCard from "@/views/commerce/printers/printer-status-card.vue";
import Printer from "@/views/commerce/printers/printer";
import { PrintersQuery } from "@/views/commerce/printers/printers-query";
import OrganizationUnitSelector from "@/views/commerce/common/selectors/organization-unit-selector.vue";
import LoadingBar from "@/components/simple/loading-bar.vue";
import TooltipIcon from "@/components/simple/tooltip-icon.vue";
import SubmitButton from "@/components/action-buttons/submit-button.vue";
import PrintersViewRow from "@/views/commerce/printers/printers-view-row.vue";

const TerminalsView = defineComponent({
  components: {
    PrintersViewRow,
    SubmitButton,
    TooltipIcon,
    LoadingBar,
    OrganizationUnitSelector,
    PrinterStatusCard,

    LoadingContainer
  },

  props: {},

  setup(props, context) {
    const router = ref(MedicaRouter.forContext(context));
    const appState = inject(AppState.Key) as AppState;
    const stateMappings = ref(StateMappings.create(appState));
    const items = ref([] as Printer[]);
    const isLoaded = ref(false);
    const isSearchLoading = ref(false);
    const query = ref(new PrintersQuery());
    const selectedItem = ref(null as Terminal | null);

    onMounted(async () => {
      router.value.getParams(query.value);
      await loadData();
    });

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

    const setQueryParams = async () => {
      await router.value.setParams(query.value.serializableQuery);
    };

    const prevPage = ref(1);
    watch(
      query,
      async (_1, _2) => {
        query.value.handleUpdate(prevPage, onSearchUpdate);
        if (isLoaded.value) await setQueryParams();
      },
      {
        deep: true
      }
    );

    const executeSearch = async () => {
      const response = await axios.post("/api/services/app/Printers/Search", query.value.dto);
      items.value = response.data.result.map(Printer.fromDto);
    };
    const refresh = async () => {
      const response = await axios.post(
        "/api/services/app/Printers/CheckAll",
        {},
        { timeout: 240000 }
      );
      items.value = response.data.result.map(Printer.fromDto);
    };

    const searchDebounce = debounce(async () => {
      await executeSearch();
      isSearchLoading.value = false;
    }, 500);

    const onSearchUpdate = () => {
      isSearchLoading.value = true;
      searchDebounce();
    };

    const allOrgUnits = computed(() => appState.orgUnits);

    const onCardClose = (needUpdate: boolean) => {
      selectedItem.value = null;
      if (needUpdate) onSearchUpdate();
    };

    return {
      items,

      query,
      refresh,
      allOrgUnits,

      isLoaded,
      isSearchLoading,
      onCardClose,
      stateMappings,
      selectedItem
    };
  }
});

export default TerminalsView;
</script>

/// occupational-factor.ts

import { GenericNamedGuidEntity } from "@/views/commerce/common/named-item";
import OccupationalConfigItemType from "@/views/commerce/occupational-medicine/occupational-config-item-type";
import { OccupationalFactorValiditySetup } from "@/views/commerce/occupational-medicine/occupational-factor-validity-setup";

export class OccupationalFactor extends GenericNamedGuidEntity {
  category: number;
  surveyName: string;
  type: OccupationalConfigItemType = OccupationalConfigItemType.Factor;
  statementValiditySetup: OccupationalFactorValiditySetup = OccupationalFactorValiditySetup.empty();
  options = 0;

  get typeModel() {
    return this.type.id;
  }

  set typeModel(val) {
    this.type = OccupationalConfigItemType.getById(val);
  }

  public static fromDto(dto: any): OccupationalFactor {
    const entity = new OccupationalFactor();
    entity.readDto(dto);
    return entity;
  }

  public static empty(category: number): OccupationalFactor {
    const entity = new OccupationalFactor();
    entity.category = category;

    return entity;
  }

  protected readDto(dto: any) {
    super.readDto(dto);
    this.type = OccupationalConfigItemType.getById(dto.type);
    this.category = dto.category;
    this.options = dto.options;
    this.surveyName = dto.surveyName || dto.name;
    this.statementValiditySetup = dto.statementValiditySetup
      ? OccupationalFactorValiditySetup.fromDto(dto.statementValiditySetup)
      : OccupationalFactorValiditySetup.empty();
  }

  public get dto(): any {
    return {
      ...super.dto,
      type: this.type.id,
      category: this.category,
      options: this.options,
      surveyName: this.surveyName || this.name,
      statementValiditySetup: this.statementValiditySetup.dto
    };
  }
}

/// occupational-factor-query.ts

import { PagedTextQuery } from "@/views/commerce/common/paged-text-query";

export class OccupationalFactorQuery extends PagedTextQuery {
  category: number | null = null;

  get serializableQuery(): any {
    return this.nonDefaultValues(new OccupationalFactorQuery());
  }

  get dto(): any {
    return {
      ...super.dto,
      category: this.category
    };
  }
}


/// occupational-factor-row.vue

<template>
  <v-row dense align="center">
    <v-col cols="auto" class="px-2">
      <span class="text-body-2"><strong>{{ value.name }}</strong>
        <span class="text-caption ml-2">{{ value.type.name }}</span>
      </span>
    </v-col>
    <v-spacer />

    <v-col cols="auto" class="pt-2">
      <edit-async-delete-buttons :readonly="readonly" :delete-action="onDelete" @select="select" />
    </v-col>
  </v-row>
</template>
<script lang="ts">
import type { PropType} from "@vue/composition-api";
import { defineComponent, inject, ref } from "@vue/composition-api";
import axios from "@/lib/axios";
import AppState from "@/views/main-wrap/app-state";
import StateMappings from "@/views/main-wrap/state-mappings";
import SubmitButton from "@/components/action-buttons/submit-button.vue";
import TooltipIcon from "@/components/simple/tooltip-icon.vue";
import InlineIcon from "@/components/simple/inline-icon.vue";
import TextUtils from "@/lib/text-utils";
import type { OccupationalFactor } from "@/views/commerce/occupational-medicine/occupational-factor";
import EditAsyncDeleteButtons from "@/components/action-buttons/edit-async-delete-buttons.vue";

const OccupationalFactorRow = defineComponent({
  components: {
    EditAsyncDeleteButtons,
    InlineIcon,
    TooltipIcon,
    SubmitButton
  },
  props: {
    value: {
      type: Object as PropType<OccupationalFactor>,
      required: true
    },
    readonly: {
      type: Boolean,
      required: false,
      default: false
    }
  },

  emits: ["update", "select"],

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

    const onDelete = async () => {
      await axios.delete("/api/services/app/OccupationalMedicine/DeleteFactor", {
        params: { id: props.value.id }
      });
      context.emit("update");
    };

    return {
      stateMappings,
      onDelete,
      trim: TextUtils.trim,
      select: () => context.emit("select")
    };
  }
});

export default OccupationalFactorRow;
</script>