USER
Mam napisaną aplikację w Vue.js. Zmodyfikuj kod, aby pod napisem icd-code-selector pojawił się v2-text-field, w którego wpisanie kodu rozpoznania icd ma powodować ten sam skutek, co wyszukanie co za pomocą icd-code-selector. Tym samym wpisanie prawidłowego kodu lcd po wciśnięciu enter ma spowodować dodanie do lcd-code-list odpowiedniego rozpoznania z code i wyszukanym name. Wyszukiwanie kodu icd i odpowiadającemu jemu opisu ma odbywać się analogicznie jak w icd-code-selector.
/// process-visit-core-card.vue
<template>
<div>
<v-row dense>
<v-col cols="12" md="4">
<template-based-input
v-model="model.interview"
:specialization-id="specializationId"
rows="14"
:readonly="readonly"
type="interview"
class="mt-2" />
</v-col>
<v-col cols="12" md="4">
<examination-card
v-model="model.examination"
:readonly="readonly"
:specialization-id="specializationId"
:type="type"
class="mt-2" />
</v-col>
<v-col cols="12" md="4">
<icd-code-list
v-if="previewMode"
v-model="model.procedures"
class="mt-2"
:readonly="readonly"
title="Zastosowane procedury"
type="Icd9" />
<slot v-else name="visit-history"></slot>
</v-col>
</v-row>
<v-row dense>
<v-col cols="12" md="4">
<icd-code-list
v-model="model.diagnoses"
title="Rozpoznania"
:readonly="readonly"
use-remarks
type="Icd10"
class="mt-2">
<template #actions="{ item }">
<slot name="diagnose-actions" :item="item"></slot>
</template>
</icd-code-list>
</v-col>
<v-col cols="12" md="4">
<text-area-with-staff-template
v-model="model.recommendations"
:readonly="readonly"
label="Zalecenia dodatkowe"
class="mt-2"
rows="10"
toolbar
high
:type="staffTemplateTypes.visitRecommendations">
<template #actions>
<submit-icon-button
icon="mdi-printer"
color="primary lighten-5"
tooltip="Drukuj"
:action="printRecommendations" />
</template>
</text-area-with-staff-template>
</v-col>
<v-col cols="12" md="4">
<medical-items-card
ref="medicalItemsRef"
:readonly="readonly"
class="mt-2"
:context="context" />
</v-col>
</v-row>
<v-row dense>
<v-col cols="12" md="8">
<slot name="patient-conditions"></slot>
</v-col>
<v-col v-if="!previewMode" cols="12" md="4">
<icd-code-list
v-model="model.procedures"
class="mt-3"
:readonly="readonly"
title="Zastosowane procedury"
type="Icd9" />
</v-col>
</v-row>
<v-row dense>
<v-col cols="12">
<ophthalmic-care-card
v-if="isOphthalmicCardType"
v-model="model.ophthalmicCare.model"
class="mt-2"
:readonly="readonly"
@print-ophthalmic="printOphthalmic" />
<surgery-care-card
v-if="isSurgeryCardType"
v-model="model.surgeryCare.model"
class="mt-2"
:specialization-id="specializationId"
:readonly="readonly" />
<gynecology-care-card
v-if="isGynecologyCardType"
v-model="model.gynecologyCare.model"
:prenatal-referral-id="prenatalReferralId"
:readonly="readonly"
@update:prenatal-referral-id="onPrenatalReferralIdUpdate" />
</v-col>
</v-row>
</div>
</template>
<script lang="ts">
import MaskUtils from "@/components/enums/mask-utils";
import SubmitIconButton from "@/components/action-buttons/submit-icon-button.vue";
import type GovServicesBaseContext from "@/views/commerce/gov-services/gov-services-base-context";
import SpecializationCardType from "@/views/commerce/specializations/specialization-card-type";
import StaffTemplateType from "@/views/commerce/staff-templates/staff-template-type";
import TextAreaWithStaffTemplate from "@/views/commerce/staff-templates/text-templates/text-area-with-staff-template.vue";
import type CareData from "@/views/commerce/visits/process/care-data";
import ExaminationCard from "@/views/commerce/visits/process/examination/examination-card.vue";
import IcdCodeList from "@/views/commerce/visits/process/icd-codes/icd-code-list.vue";
import MedicalItemsCard from "@/views/commerce/visits/process/medical-data/medical-items-card.vue";
import OphthalmicCareCard from "@/views/commerce/visits/process/ophthalmic/ophthalmic-care-card.vue";
import SurgeryCareCard from "@/views/commerce/visits/process/surgery/surgery-care-card.vue";
import TemplateBasedInput from "@/views/commerce/visits/summary-template/template-based-input.vue";
import type {
PropType} from "@vue/composition-api";
import {
computed,
defineComponent,
inject,
onBeforeUpdate,
onMounted,
ref,
watch
} from "@vue/composition-api";
import { objectProp } from "@/compatibility-layer/model-prop";
import ModelConstants from "@/compatibility-layer/model-constants";
import { useObjectModel } from "@/compatibility-layer/object-model";
import GynecologyCareCard from "@/views/commerce/visits/process/gynecology/gynecology-care-card.vue";
import FixedData from "@/views/main-wrap/fixed-data";
import PregnancyCodes from "@/views/commerce/visits/process/pregnancy-codes";
const ProcessVisitCoreCard = defineComponent({
components: {
GynecologyCareCard,
SubmitIconButton,
SurgeryCareCard,
OphthalmicCareCard,
IcdCodeList,
MedicalItemsCard,
ExaminationCard,
TextAreaWithStaffTemplate,
TemplateBasedInput
},
props: {
...objectProp<CareData>(),
type: {
type: Number,
required: true
},
specializationId: {
type: Number,
required: true
},
prenatalReferralId: {
type: [String, null] as PropType<string | null>,
required: false,
default: null
},
previewMode: {
type: Boolean,
required: false,
default: false
},
context: {
type: Object as PropType<GovServicesBaseContext>,
required: true
}
},
emits: [
ModelConstants.modelEvent,
"print-ophthalmic",
"print-recommendations",
"update:prenatal-referral-id"
],
setup(props, context) {
const fixedData = inject(FixedData.Key) as FixedData;
const model = useObjectModel<CareData>(props, context);
const pregnancyCodes = ref(PregnancyCodes.empty());
const inPatientConditionsEditMode = ref(false);
const medicalItemsRef = ref(null);
onBeforeUpdate(() => {
medicalItemsRef.value = null;
});
onMounted(async () => {
pregnancyCodes.value = await fixedData.loadPregnancyCodes();
});
const isSurgeryCardType = computed(() =>
MaskUtils.hasFlag(props.type, SpecializationCardType.Surgery)
);
const isOphthalmicCardType = computed(() =>
MaskUtils.hasFlag(props.type, SpecializationCardType.Ophthalmic)
);
const isAestheticCardType = computed(() =>
MaskUtils.hasFlag(props.type, SpecializationCardType.Aesthetic)
);
const isGynecologyCardType = computed(() =>
MaskUtils.hasFlag(props.type, SpecializationCardType.Gynecology)
);
const printRecommendations = (): Promise<void> =>
new Promise<void>(resolve => context.emit("print-recommendations", resolve));
const reload = () => {
const entity = medicalItemsRef.value as any;
if (entity) {
entity.reload();
}
};
const pregnancyImplied = ref(false);
watch(
() => model.value.diagnoses,
(nv, _) => {
if (pregnancyImplied.value) return;
if (!isGynecologyCardType.value) return;
if (nv.find(v => pregnancyCodes.value.all.has(v.code))) {
model.value.gynecologyCare.isPregnant = true;
pregnancyImplied.value = true;
}
},
{ deep: true }
);
return {
model,
isSurgeryCardType,
isOphthalmicCardType,
isAestheticCardType,
isGynecologyCardType,
inPatientConditionsEditMode,
printOphthalmic: resolve => context.emit("print-ophthalmic", resolve),
printRecommendations,
medicalItemsRef,
reload,
staffTemplateTypes: StaffTemplateType.instance,
onPrenatalReferralIdUpdate: v => context.emit("update:prenatal-referral-id", v)
};
}
});
export default ProcessVisitCoreCard;
</script>
/// v2-text-field.vue
<template>
<v-text-field
v-model="model"
:clearable="!readonly && clearable"
:dense="dense"
:filled="readonly || filled"
:hide-details="hideDetails || rules.length === 0"
:label="label"
:placeholder="placeholder"
:prepend-icon="prependIcon"
:readonly="readonly"
:rules="computedRules"
:loading="loading"
:error-messages="errorMessages"
:autocomplete="autocomplete"
:outlined="outlined"
:type="type"
@click="onClick"
@click:clear="onClear">
<template v-if="customPrependInner" #prepend-inner>
<slot name="prepend-inner"></slot>
</template>
</v-text-field>
</template>
<script lang="ts">
import ModelConstants from "@/compatibility-layer/model-constants";
import { optionalStringProp } from "@/compatibility-layer/model-prop";
import { useOptionalStringModel } from "@/compatibility-layer/primitive-model";
import { RulesUtils } from "@/lib/rules-utils";
import type { PropType } from "@vue/composition-api";
import { computed, defineComponent } from "@vue/composition-api";
import type { HideDetailsType } from "@/compatibility-layer/hide-details-type";
const V2TextField = defineComponent({
props: {
...optionalStringProp(),
label: {
type: String,
required: false,
default: null
},
placeholder: {
type: String,
required: false,
default: null
},
rules: {
type: Array,
required: false,
default: () => []
},
prependIcon: {
type: String,
required: false,
default: undefined
},
clearable: {
type: Boolean,
required: false,
default: false
},
dense: {
type: Boolean,
required: false,
default: true
},
loading: {
type: Boolean,
required: false,
default: false
},
outlined: {
type: Boolean,
required: false,
default: true
},
hideDetails: {
type: [String, Boolean] as PropType<HideDetailsType>,
required: false,
default: true
},
required: {
type: Boolean,
required: false,
default: false
},
filled: {
type: Boolean,
required: false,
default: false
},
autocomplete: {
type: String,
required: false,
default: "off"
},
type: {
type: String,
required: false,
default: "text"
},
errorMessages: {
type: Array,
required: false,
default: () => []
},
customPrependInner: {
type: Boolean,
required: false,
default: false
}
},
emits: [ModelConstants.modelEvent, "click", "clear"],
setup(props, context) {
const model = useOptionalStringModel(props, context);
const computedRules = computed(() => {
return props.required ? [...RulesUtils.NotEmpty, ...props.rules] : props.rules;
});
return {
model,
computedRules,
onClick: () => context.emit("click"),
onClear: () => context.emit("clear")
};
}
});
export default V2TextField;
</script>
/// icd-code-list.vue
<template>
<titled-card toolbar :title="title" :add-button="!readonly" @add="addItem">
<v-overlay v-if="inAddItemMode" z-index="30" :dark="false">
<icd-code-selector
:endpoint-suffix="type"
@select="submitAddItem"
@close="inAddItemMode = false" />
</v-overlay>
<v-overlay v-if="showDescDialog" z-index="30" :dark="false">
<async-input-dialog
title="Opis"
style="min-width: min(640px, 95vw)"
:action="setDescription"
:rules="descriptionRules"
:default-value="selectedItemRemarks"
multiline
@close="editedIndex = null" />
</v-overlay>
<template v-for="(item, idx) in model">
<v-row :key="idx" dense align="center">
<v-col cols="auto" style="flex-basis: 56px">
<span class="text--bold text-body-2" v-text="item.code"></span>
</v-col>
<v-col>
<div class="text-body-2" v-text="item.name"></div>
<div v-if="item.remarks" class="text--darken-3 text-caption primary--text">
{{ item.remarks }}
</div>
</v-col>
<v-col cols="auto">
<slot name="actions" :item="item"></slot>
</v-col>
<v-col v-if="!readonly" cols="auto">
<edit-delete-buttons
:edit-visible="useRemarks"
@delete="deleteItem(item)"
@edit="editedIndex = idx" />
</v-col>
</v-row>
<v-divider v-if="idx < model.length - 1" :key="idx" class="my-1" />
</template>
<span v-if="!model.length" class="text-caption">Brak</span>
<template #actions>
<v-row dense align="center">
<v-col cols="auto">
<copy-from-toolbar v-if="!readonly" :action="copyFromSelection" :type="listType" />
</v-col>
</v-row>
</template>
</titled-card>
</template>
<script lang="ts">
import ModelConstants from "@/compatibility-layer/model-constants";
import { arrayProp } from "@/compatibility-layer/model-prop";
import { useArrayModel } from "@/compatibility-layer/object-model";
import AsyncInputDialog from "@/components/action-buttons/async-input-dialog.vue";
import EditDeleteButtons from "@/components/action-buttons/edit-delete-buttons.vue";
import LoadingContainer from "@/components/loading/loading-container.vue";
import TitledCard from "@/components/simple/titled-card.vue";
import CopyFromToolbar from "@/views/commerce/visits/process/copy-from-selected-visit/copy-from-toolbar.vue";
import type IcdCode from "@/views/commerce/visits/process/icd-codes/icd-code";
import IcdCodeSelector from "@/views/commerce/visits/process/icd-codes/icd-code-selector.vue";
import type RecentProcessedVisit from "@/views/commerce/visits/process/recent-processed-visit";
import type { CopyFromVisitInputType } from "@/views/commerce/visits/summary-template/copy-from-visit-input-type";
import { computed, defineComponent, ref } from "@vue/composition-api";
import { RulesUtils } from "@/lib/rules-utils";
const IcdCodeListSelector = defineComponent({
components: {
CopyFromToolbar,
AsyncInputDialog,
TitledCard,
IcdCodeSelector,
EditDeleteButtons,
LoadingContainer
},
props: {
...arrayProp<IcdCode>(),
type: {
type: String,
required: true
},
title: {
type: String,
required: true
},
useRemarks: {
type: Boolean,
required: false,
default: false
}
},
emits: [ModelConstants.modelEvent],
setup(props, context) {
const model = useArrayModel<IcdCode>(props, context, {
itemKey: v => `${v.code}_${v.remarks}`
});
const editedIndex = ref(null as number | null);
const inAddItemMode = ref(false);
const setDescription = (val: string) => {
const idxVal = editedIndex.value;
if (idxVal || idxVal === 0) {
model.value[idxVal].remarks = val;
}
editedIndex.value = null;
return Promise.resolve();
};
const addItem = () => {
inAddItemMode.value = true;
};
const deleteItem = (val: IcdCode) => {
const idx = model.value.findIndex(v => v.code === val.code);
if (idx !== -1) {
model.value.splice(idx, 1);
}
};
const submitAddItem = (val: IcdCode) => {
if (model.value.findIndex(v => v.code === val.code) === -1) {
model.value.push(val);
}
inAddItemMode.value = false;
};
const listType = computed(() => {
if (props.type === "Icd9") {
return "procedures" as CopyFromVisitInputType;
}
if (props.type === "Icd10") {
return "diagnoses" as CopyFromVisitInputType;
}
return null;
});
const getLoadedList = (visit: RecentProcessedVisit): IcdCode[] => {
if (props.type === "Icd9") {
return visit.patientCare.procedures;
}
if (props.type === "Icd10") {
return visit.patientCare.diagnoses;
}
return [];
};
const copyFromSelection = (visit: RecentProcessedVisit) => {
const loadedList = getLoadedList(visit);
if (!loadedList.length) {
return;
}
model.value = loadedList;
};
const showDescDialog = computed(() => {
const idxVal = editedIndex.value;
return idxVal || idxVal === 0;
});
const selectedItemRemarks = computed(() => {
const idxVal = editedIndex.value;
return idxVal || idxVal === 0 ? model.value[idxVal].remarks : null;
});
return {
model,
deleteItem,
addItem,
copyFromSelection,
submitAddItem,
listType,
setDescription,
editedIndex,
showDescDialog,
selectedItemRemarks,
descriptionRules: RulesUtils.ExcludedHtmlControlSequences(),
inAddItemMode
};
}
});
export default IcdCodeListSelector;
</script>
/// icd-code-selector.vue
<template>
<titled-card
title="Wybierz pozycję"
close-button
toolbar
style="width: min(1000px, 90vw)"
@close="close">
<template v-if="showAll">
<v-row dense align="start" class="my-0">
<v-col>
<v-tabs v-if="!hideCategories" v-model="currentTab">
<v-tab>Lista</v-tab>
<v-tab>Kategorie</v-tab>
</v-tabs>
</v-col>
<v-col v-if="currentTab === 0" cols="auto">
<search-input v-model="searchPhrase" autofocus />
</v-col>
</v-row>
<v-tabs-items v-model="currentTab">
<v-tab-item>
<v-container
class="py-0"
fluid
style="height: calc(100vh - 250px); min-height: 60vh; overflow-y: scroll">
<icd-code-list-selector
:endpoint-suffix="endpointSuffix"
:search-phrase="searchPhrase"
@select="select" />
</v-container>
</v-tab-item>
<v-tab-item>
<v-container
fluid
style="max-height: calc(100vh - 250px); min-height: 60vh; overflow-y: scroll">
<icd-code-tree-selector :endpoint-suffix="endpointSuffix" @select="select" />
</v-container>
</v-tab-item>
</v-tabs-items>
</template>
<template v-else>
<v-row dense justify="end" align="center">
<v-col>
<v-btn text small color="primary" @click="showAll = true">
Pokaż wszystkie
</v-btn>
</v-col>
<v-col cols="auto">
<search-input v-model="searchPhrase" autofocus />
</v-col>
</v-row>
<icd-code-list-selector
:endpoint-suffix="endpointSuffix"
:search-phrase="searchPhrase"
:search-prefix="searchPrefix"
@select="select" />
</template>
</titled-card>
</template>
<script lang="ts">
import SearchInput from "@/components/simple/search-input.vue";
import TitledCard from "@/components/simple/titled-card.vue";
import type IcdCode from "@/views/commerce/visits/process/icd-codes/icd-code";
import IcdCodeListSelector from "@/views/commerce/visits/process/icd-codes/icd-code-list-selector.vue";
import IcdCodeTreeSelector from "@/views/commerce/visits/process/icd-codes/icd-code-tree-selector.vue";
import { defineComponent, ref } from "@vue/composition-api";
const MedicalServiceGroupsView = defineComponent({
components: { SearchInput, TitledCard, IcdCodeTreeSelector, IcdCodeListSelector },
props: {
endpointSuffix: {
type: String,
required: true
},
hideCategories: {
type: Boolean,
required: false,
default: false
},
searchPrefix: {
type: String,
required: false,
default: null
}
},
setup(props, context) {
const searchPhrase = ref(null as string | null);
const showAll = ref(!props.searchPrefix);
const currentTab = ref(0);
const select = (item: IcdCode) => {
context.emit("select", item);
};
const close = () => {
searchPhrase.value = null;
context.emit("close");
};
return {
searchPhrase,
select,
close,
showAll,
currentTab
};
}
});
export default MedicalServiceGroupsView;
</script>
/// icd-code-list-selector.vue
<template>
<v-container class="ma-0 pa-0" fluid>
<div style="min-height: 5px">
<v-progress-linear v-if="isSearchLoading" indeterminate color="primary" />
</div>
<v-data-iterator
:footer-props="{ itemsPerPageOptions: iteratorOptions.itemsPerPageOptions }"
:items="items"
:items-per-page.sync="query.itemsPerPage"
:page.sync="query.page"
:server-items-length="totalCount"
:loading="isSearchLoading"
disable-filtering
disable-sort
class="pb-6 pt-3">
<template #loading>
<list-loading />
</template>
<template #no-data>
<v-card><v-card-text>Brak wpisów spełniających kryteria</v-card-text></v-card>
</template>
<template #default="props">
<template v-for="(item, index) in items">
<v-card :key="index" flat tile @click="select(item)">
<v-row :key="index" dense>
<v-col cols="auto">
<div class="text--bold text-body-2" style="min-width: 60px" v-text="item.code"></div>
</v-col>
<v-col>
<span class="text-body-2" v-text="item.name"></span>
</v-col>
</v-row>
</v-card>
<v-divider v-if="index < items.length - 1" :key="index" class="my-1" />
</template>
</template>
</v-data-iterator>
</v-container>
</template>
<script lang="ts">
import axios from "@/lib/axios";
import { defineComponent, onMounted, ref, watch } from "@vue/composition-api";
import { debounce } from "lodash";
import LoadingContainer from "@/components/loading/loading-container.vue";
import IcdCode from "@/views/commerce/visits/process/icd-codes/icd-code";
import { IcdCodeQuery } from "@/views/commerce/visits/process/icd-codes/icd-code-query";
import ListLoading from "@/components/loading/list-loading.vue";
const IcdCodeListSelector = defineComponent({
components: { LoadingContainer, ListLoading },
props: {
endpointSuffix: {
type: String,
required: true
},
searchPhrase: {
type: String,
required: true
},
searchPrefix: {
type: String,
required: false,
default: null
}
},
emits: ["select"],
setup(props, context) {
const isLoaded = ref(false);
const isSearchLoading = ref(false);
const query = ref(new IcdCodeQuery());
const items = ref([] as IcdCode[]);
const totalCount = ref(0);
const iteratorOptions = ref({
itemsPerPageOptions: [15, 30]
});
const loadData = async () => {
isLoaded.value = false;
await executeSearch();
isLoaded.value = true;
};
onMounted(async () => {
query.value.itemsPerPage = 30;
query.value.prefix = props.searchPrefix;
query.value.searchPhrase = props.searchPhrase;
await loadData();
isLoaded.value = true;
});
watch(
() => props.searchPhrase,
(nv, _) => (query.value.searchPhrase = nv)
);
watch(
() => props.searchPrefix,
(nv, _) => (query.value.prefix = nv)
);
const prevPage = ref(1);
watch(
query,
async (nv, _) => {
if (!isLoaded.value) return;
query.value.handleUpdate(prevPage, onSearchUpdate);
},
{
deep: true
}
);
const executeSearch = async () => {
const response = await axios.post(
`/api/services/app/IcdCodes/Search${props.endpointSuffix}`,
query.value.dto
);
items.value = response.data.result.items.map(IcdCode.fromDto);
totalCount.value = response.data.result.totalCount;
};
const searchDebounce = debounce(async () => {
await executeSearch();
isSearchLoading.value = false;
}, 500);
const onSearchUpdate = () => {
isSearchLoading.value = true;
searchDebounce();
};
const select = (item: IcdCode) => {
context.emit("select", item);
};
return {
query,
totalCount,
items,
isLoaded,
isSearchLoading,
iteratorOptions,
select
};
}
});
export default IcdCodeListSelector;
</script>
ASSISTANT
Aby dodać pole tekstowe (`v2-text-field`) poniżej komponentu `icd-code-selector`, które umożliwi ręczne wprowadzanie kodów ICD i dodawanie ich do listy rozpoznań (`lcd-code-list`), należy wykonać następujące kroki:
1. **Import komponentu `V2TextField`.**
2. **Dodanie nowej właściwości (`icdCodeInput`) do modelu.**
3. **Stworzenie metody obsługującej wprowadzenie kodu i dodawanie go do listy rozpoznań.**
4. **Dodanie komponentu `V2TextField` do szablonu HTML.**
Poniżej przedstawiam szczegółową modyfikację pliku `process-visit-core-card.vue`:
### 1. Importowanie komponentu `V2TextField`
Najpierw musisz zaimportować komponent `V2TextField` w sekcji `<script>`:
```typescript
<script lang="ts">
import MaskUtils from "@/components/enums/mask-utils";
import SubmitIconButton from "@/components/action-buttons/submit-icon-button.vue";
import type GovServicesBaseContext from "@/views/commerce/gov-services/gov-services-base-context";
import SpecializationCardType from "@/views/commerce/specializations/specialization-card-type";
import StaffTemplateType from "@/views/commerce/staff-templates/staff-template-type";
import TextAreaWithStaffTemplate from "@/views/commerce/staff-templates/text-templates/text-area-with-staff-template.vue";
import type CareData from "@/views/commerce/visits/process/care-data";
import ExaminationCard from "@/views/commerce/visits/process/examination/examination-card.vue";
import IcdCodeList from "@/views/commerce/visits/process/icd-codes/icd-code-list.vue";
import MedicalItemsCard from "@/views/commerce/visits/process/medical-data/medical-items-card.vue";
import OphthalmicCareCard from "@/views/commerce/visits/process/ophthalmic/ophthalmic-care-card.vue";
import SurgeryCareCard from "@/views/commerce/visits/process/surgery/surgery-care-card.vue";
import TemplateBasedInput from "@/views/commerce/visits/summary-template/template-based-input.vue";
import type {
PropType
} from "@vue/composition-api";
import {
computed,
defineComponent,
inject,
onBeforeUpdate,
onMounted,
ref,
watch
} from "@vue/composition-api";
import { objectProp } from "@/compatibility-layer/model-prop";
import ModelConstants from "@/compatibility-layer/model-constants";
import { useObjectModel } from "@/compatibility-layer/object-model";
import GynecologyCareCard from "@/views/commerce/visits/process/gynecology/gynecology-care-card.vue";
import FixedData from "@/views/main-wrap/fixed-data";
import PregnancyCodes from "@/views/commerce/visits/process/pregnancy-codes";
import V2TextField from "@/components/input/v2-text-field.vue"; // Dodany import
const ProcessVisitCoreCard = defineComponent({
components: {
GynecologyCareCard,
SubmitIconButton,
SurgeryCareCard,
OphthalmicCareCard,
IcdCodeList,
MedicalItemsCard,
ExaminationCard,
TextAreaWithStaffTemplate,
TemplateBasedInput,
V2TextField // Dodany komponent
},
// ... pozostała część kodu propsów, emits itp.
setup(props, context) {
const fixedData = inject(FixedData.Key) as FixedData;
const model = useObjectModel<CareData>(props, context);
const pregnancyCodes = ref(PregnancyCodes.empty());
const inPatientConditionsEditMode = ref(false);
const medicalItemsRef = ref(null);
onBeforeUpdate(() => {
medicalItemsRef.value = null;
});
onMounted(async () => {
pregnancyCodes.value = await fixedData.loadPregnancyCodes();
});
const isSurgeryCardType = computed(() =>
MaskUtils.hasFlag(props.type, SpecializationCardType.Surgery)
);
const isOphthalmicCardType = computed(() =>
MaskUtils.hasFlag(props.type, SpecializationCardType.Ophthalmic)
);
const isAestheticCardType = computed(() =>
MaskUtils.hasFlag(props.type, SpecializationCardType.Aesthetic)
);
const isGynecologyCardType = computed(() =>
MaskUtils.hasFlag(props.type, SpecializationCardType.Gynecology)
);
const printRecommendations = (): Promise<void> =>
new Promise<void>(resolve => context.emit("print-recommendations", resolve));
const reload = () => {
const entity = medicalItemsRef.value as any;
if (entity) {
entity.reload();
}
};
const pregnancyImplied = ref(false);
watch(
() => model.value.diagnoses,
(nv, _) => {
if (pregnancyImplied.value) return;
if (!isGynecologyCardType.value) return;
if (nv.find(v => pregnancyCodes.value.all.has(v.code))) {
model.value.gynecologyCare.isPregnant = true;
pregnancyImplied.value = true;
}
},
{ deep: true }
);
// Nowa właściwość do przechowywania wprowadzonego kodu ICD
const icdCodeInput = ref("");
// Obsługa dodawania kodu ICD po naciśnięciu Enter
const handleIcdCodeInput = async () => {
const code = icdCodeInput.value.trim().toUpperCase();
if (!code) return;
// Sprawdzenie formatu kodu ICD-10 (przykład: A00, B99.9)
const icd10Regex = /^[A-TV-Z][0-9][0-9A-TV-Z]([.][0-9A-TV-Z]{1,2})?$/;
if (!icd10Regex.test(code)) {
alert("Nieprawidłowy format kodu ICD-10.");
return;
}
try {
// Wywołanie API w celu wyszukania opisu dla podanego kodu
const response = await fetch(`/api/services/app/IcdCodes/SearchIcd10`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
searchPhrase: code
})
});
if (!response.ok) {
throw new Error("Błąd sieciowy.");
}
const data = await response.json();
const foundItem = data.result.items.find((item: any) => item.code === code);
if (foundItem) {
// Sprawdzenie, czy kod już istnieje na liście
const exists = model.value.diagnoses.some((diag: any) => diag.code === code);
if (exists) {
alert("Ten kod ICD-10 już istnieje na liście rozpoznań.");
} else {
// Dodanie kodu do listy rozpoznań
model.value.diagnoses.push({
code: foundItem.code,
name: foundItem.name,
remarks: ""
});
icdCodeInput.value = ""; // Czyszczenie pola tekstowego
}
} else {
alert("Nie znaleziono rozpoznania dla podanego kodu ICD-10.");
}
} catch (error) {
console.error(error);
alert("Wystąpił błąd podczas wyszukiwania kodu ICD-10.");
}
};
return {
model,
isSurgeryCardType,
isOphthalmicCardType,
isAestheticCardType,
isGynecologyCardType,
inPatientConditionsEditMode,
printOphthalmic: resolve => context.emit("print-ophthalmic", resolve),
printRecommendations,
medicalItemsRef,
reload,
staffTemplateTypes: StaffTemplateType.instance,
onPrenatalReferralIdUpdate: v => context.emit("update:prenatal-referral-id", v),
icdCodeInput, // Eksport nowej właściwości
handleIcdCodeInput // Eksport nowej metody
};
}
});
</script>
```
### 2. Modyfikacja szablonu HTML
Dodaj komponent `V2TextField` poniżej komponentu `icd-code-list` dla rozpoznań (`diagnoses`). Upewnij się, że odpowiednio ustawisz etykietę, placeholder oraz powiążesz metodę obsługującą zdarzenie `keyup.enter`.
```html
<template>
<div>
<v-row dense>
<v-col cols="12" md="4">
<template-based-input
v-model="model.interview"
:specialization-id="specializationId"
rows="14"
:readonly="readonly"
type="interview"
class="mt-2" />
</v-col>
<v-col cols="12" md="4">
<examination-card
v-model="model.examination"
:readonly="readonly"
:specialization-id="specializationId"
:type="type"
class="mt-2" />
</v-col>
<v-col cols="12" md="4">
<icd-code-list
v-if="previewMode"
v-model="model.procedures"
class="mt-2"
:readonly="readonly"
title="Zastosowane procedury"
type="Icd9" />
<slot v-else name="visit-history"></slot>
</v-col>
</v-row>
<v-row dense>
<v-col cols="12" md="4">
<icd-code-list
v-model="model.diagnoses"
title="Rozpoznania"
:readonly="readonly"
use-remarks
type="Icd10"
class="mt-2">
<template #actions="{ item }">
<slot name="diagnose-actions" :item="item"></slot>
</template>
</icd-code-list>
<!-- Dodanie V2TextField poniżej icd-code-list -->
<v2-text-field
v-model="icdCodeInput"
label="Wprowadź kod ICD-10"
placeholder="np. A00.0"
@keyup.enter="handleIcdCodeInput"
:filled="true"
class="mt-2" />
</v-col>
<v-col cols="12" md="4">
<text-area-with-staff-template
v-model="model.recommendations"
:readonly="readonly"
label="Zalecenia dodatkowe"
class="mt-2"
rows="10"
toolbar
high
:type="staffTemplateTypes.visitRecommendations">
<template #actions>
<submit-icon-button
icon="mdi-printer"
color="primary lighten-5"
tooltip="Drukuj"
:action="printRecommendations" />
</template>
</text-area-with-staff-template>
</v-col>
<v-col cols="12" md="4">
<medical-items-card
ref="medicalItemsRef"
:readonly="readonly"
class="mt-2"
:context="context" />
</v-col>
</v-row>
<!-- Pozostała część szablonu -->
</div>
</template>
```
### 3. Wyjaśnienie dodanych elementów
- **`icdCodeInput`**: Nowa właściwość w modelu, która przechowuje wprowadzony kod ICD-10.
- **`handleIcdCodeInput`**: Metoda, która jest wywoływana po naciśnięciu klawisza Enter w polu tekstowym. Metoda:
- Sprawdza poprawność formatu wprowadzonego kodu.
- Wysyła zapytanie do API w celu wyszukania opisu kodu ICD-10.
- Jeśli kod zostanie znaleziony i nie znajduje się już na liście, dodaje go do `model.diagnoses`.
- Jeśli kod nie zostanie znaleziony lub już istnieje na liście, wyświetla odpowiedni komunikat.
- **`V2TextField`**: Dodany komponent, który umożliwia użytkownikowi ręczne wprowadzanie kodów ICD-10. Jest powiązany z `icdCodeInput` i reaguje na zdarzenie `keyup.enter`, aby uruchomić metodę `handleIcdCodeInput`.
### 4. Uwagi dodatkowe
- **Walidacja kodu ICD-10**: Wyrażenie regularne użyte w metodzie `handleIcdCodeInput` jest przykładem i może wymagać dostosowania do specyficznych wymagań formatu kodów ICD-10 w Twojej aplikacji.
- **Obsługa błędów**: W metodzie `handleIcdCodeInput` użyto prostych alertów do informowania użytkownika o błędach. W zależności od Twojej aplikacji, możesz chcieć zastosować bardziej zaawansowane mechanizmy powiadamiania, takie jak snackbar lub modale.
- **Stylizacja**: Klasa `mt-2` dodana do komponentu `V2TextField` zapewnia margines górny. Możesz dostosować klasy CSS w zależności od potrzeb projektowych.
- **Optymalizacja**: Aby uniknąć nadmiernych zapytań do API, można dodać throttling lub debounce w metodzie obsługującej wprowadzenie kodu.
### 5. Alternatywne podejście - Modyfikacja `icd-code-list.vue`
Jeśli preferujesz, aby pole tekstowe było częścią komponentu `icd-code-list.vue`, możesz zastosować podobne zmiany bezpośrednio w tym komponencie. Oto skrócony przykład:
```vue
<!-- icd-code-list.vue -->
<template>
<titled-card toolbar :title="title" :add-button="!readonly" @add="addItem">
<!-- Istniejące elementy -->
<v-overlay v-if="inAddItemMode" z-index="30" :dark="false">
<icd-code-selector
:endpoint-suffix="type"
@select="submitAddItem"
@close="inAddItemMode = false" />
<!-- Dodanie V2TextField w trybie dodawania -->
<v2-text-field
v-model="icdCodeInput"
label="Wprowadź kod ICD-10"
placeholder="np. A00.0"
@keyup.enter="handleIcdCodeInput"
:filled="true"
class="mt-2" />
</v-overlay>
<!-- Pozostała część szablonu -->
</titled-card>
</template>
<script lang="ts">
import ModelConstants from "@/compatibility-layer/model-constants";
import { arrayProp } from "@/compatibility-layer/model-prop";
import { useArrayModel } from "@/compatibility-layer/object-model";
import AsyncInputDialog from "@/components/action-buttons/async-input-dialog.vue";
import EditDeleteButtons from "@/components/action-buttons/edit-delete-buttons.vue";
import LoadingContainer from "@/components/loading/loading-container.vue";
import TitledCard from "@/components/simple/titled-card.vue";
import CopyFromToolbar from "@/views/commerce/visits/process/copy-from-selected-visit/copy-from-toolbar.vue";
import type IcdCode from "@/views/commerce/visits/process/icd-codes/icd-code";
import IcdCodeSelector from "@/views/commerce/visits/process/icd-codes/icd-code-selector.vue";
import type RecentProcessedVisit from "@/views/commerce/visits/process/recent-processed-visit";
import type { CopyFromVisitInputType } from "@/views/commerce/visits/summary-template/copy-from-visit-input-type";
import { computed, defineComponent, ref } from "@vue/composition-api";
import { RulesUtils } from "@/lib/rules-utils";
import V2TextField from "@/components/input/v2-text-field.vue"; // Dodany import
const IcdCodeListSelector = defineComponent({
components: {
CopyFromToolbar,
AsyncInputDialog,
TitledCard,
IcdCodeSelector,
EditDeleteButtons,
LoadingContainer,
V2TextField // Dodany komponent
},
props: {
...arrayProp<IcdCode>(),
type: {
type: String,
required: true
},
title: {
type: String,
required: true
},
useRemarks: {
type: Boolean,
required: false,
default: false
}
},
emits: [ModelConstants.modelEvent],
setup(props, context) {
const model = useArrayModel<IcdCode>(props, context, {
itemKey: v => `${v.code}_${v.remarks}`
});
const editedIndex = ref(null as number | null);
const inAddItemMode = ref(false);
const icdCodeInput = ref(""); // Nowa właściwość
const setDescription = (val: string) => {
const idxVal = editedIndex.value;
if (idxVal || idxVal === 0) {
model.value[idxVal].remarks = val;
}
editedIndex.value = null;
return Promise.resolve();
};
const addItem = () => {
inAddItemMode.value = true;
};
const deleteItem = (val: IcdCode) => {
const idx = model.value.findIndex(v => v.code === val.code);
if (idx !== -1) {
model.value.splice(idx, 1);
}
};
const submitAddItem = (val: IcdCode) => {
if (model.value.findIndex(v => v.code === val.code) === -1) {
model.value.push(val);
}
inAddItemMode.value = false;
};
const handleIcdCodeInput = async () => {
const code = icdCodeInput.value.trim().toUpperCase();
if (!code) return;
const icd10Regex = /^[A-TV-Z][0-9][0-9A-TV-Z]([.][0-9A-TV-Z]{1,2})?$/;
if (!icd10Regex.test(code)) {
alert("Nieprawidłowy format kodu ICD-10.");
return;
}
try {
const response = await fetch(`/api/services/app/IcdCodes/SearchIcd10`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
searchPhrase: code
})
});
if (!response.ok) {
throw new Error("Błąd sieciowy.");
}
const data = await response.json();
const foundItem = data.result.items.find((item: any) => item.code === code);
if (foundItem) {
const exists = model.value.some((diag: any) => diag.code === code);
if (exists) {
alert("Ten kod ICD-10 już istnieje na liście rozpoznań.");
} else {
model.value.push({
code: foundItem.code,
name: foundItem.name,
remarks: ""
});
icdCodeInput.value = "";
}
} else {
alert("Nie znaleziono rozpoznania dla podanego kodu ICD-10.");
}
} catch (error) {
console.error(error);
alert("Wystąpił błąd podczas wyszukiwania kodu ICD-10.");
}
};
const listType = computed(() => {
if (props.type === "Icd9") {
return "procedures" as CopyFromVisitInputType;
}
if (props.type === "Icd10") {
return "diagnoses" as CopyFromVisitInputType;
}
return null;
});
const getLoadedList = (visit: RecentProcessedVisit): IcdCode[] => {
if (props.type === "Icd9") {
return visit.patientCare.procedures;
}
if (props.type === "Icd10") {
return visit.patientCare.diagnoses;
}
return [];
};
const copyFromSelection = (visit: RecentProcessedVisit) => {
const loadedList = getLoadedList(visit);
if (!loadedList.length) {
return;
}
model.value = loadedList;
};
const showDescDialog = computed(() => {
const idxVal = editedIndex.value;
return idxVal || idxVal === 0;
});
const selectedItemRemarks = computed(() => {
const idxVal = editedIndex.value;
return idxVal || idxVal === 0 ? model.value[idxVal].remarks : null;
});
return {
model,
deleteItem,
addItem,
copyFromSelection,
submitAddItem,
listType,
setDescription,
editedIndex,
showDescDialog,
selectedItemRemarks,
descriptionRules: RulesUtils.ExcludedHtmlControlSequences(),
inAddItemMode,
icdCodeInput, // Eksport nowej właściwości
handleIcdCodeInput // Eksport nowej metody
};
}
});
export default IcdCodeListSelector;
</script>
```
### Podsumowanie
Powyższe zmiany umożliwiają użytkownikowi ręczne wprowadzanie kodów ICD-10 za pomocą `V2TextField`. Po wprowadzeniu kodu i naciśnięciu Enter, aplikacja sprawdza poprawność formatu kodu, wyszukuje opis za pośrednictwem API, a następnie dodaje rozpoznanie do listy, jeśli kod jest prawidłowy i nie został jeszcze dodany.
Pamiętaj, aby dostosować ścieżki API oraz walidację kodów ICD-10 do specyfiki Twojej aplikacji. Dodatkowo, możesz rozważyć bardziej zaawansowane metody obsługi błędów i powiadomień, aby poprawić doświadczenie użytkownika.