6 Commits
dd ... master

Author SHA1 Message Date
kandrusyak
4459b5df48 add multiple select 2025-01-31 22:00:04 +03:00
Dmitriy Derbentsov
7e18a794bf Merge branch 'ref-Med-Card' into 'master'
first step

See merge request astra/astra-frontend!514
2023-10-14 16:33:10 +00:00
kandrusyak
564860883e add validation to create event form 2023-10-13 02:34:46 +03:00
Kirill Andrusyak
2b4d04ae3a Merge branch 'move-tax-identification-number' into 'master'
Поправила баги, сдвинула ИНН

See merge request astra/astra-frontend!512
2023-10-09 00:19:41 +00:00
Dmitriy Derbentsov
c0eaad8e76 Merge branch 'dd-1' into 'master'
поиск карт

See merge request astra/astra-frontend!513
2023-10-08 21:26:03 +00:00
Daria Golova
a0b186a8d0 Поправила баги, сдвинула ИНН 2023-10-04 14:03:38 +03:00
32 changed files with 350 additions and 740 deletions

View File

@@ -1,6 +1,6 @@
<template lang="pug"> <template lang="pug">
teleport(:to="appContainer") teleport(:to="appContainer")
.absolute.top-0.p-2.right-0.overflow-hidden.z-50(class="w-1/4 xl:w-1/3 sm:w-1/2") .absolute.top-0.p-2.right-0.overflow-hidden.wrapper(class="w-1/4 xl:w-1/3 sm:w-1/2")
.pt-32(v-if="displayPadding") .pt-32(v-if="displayPadding")
.flex.gap-2.flex-col.relative .flex.gap-2.flex-col.relative
transition-group(name="list", @before-leave="displayPadding = true", @after-leave="displayPadding = false") transition-group(name="list", @before-leave="displayPadding = true", @after-leave="displayPadding = false")
@@ -41,6 +41,9 @@ export default {
</script> </script>
<style scoped> <style scoped>
.wrapper {
z-index: 10000;
}
.list-enter-active, .list-enter-active,
.list-leave-active { .list-leave-active {
transition: all 0.5s ease; transition: all 0.5s ease;

View File

@@ -2,7 +2,7 @@ import { reactive } from "vue";
export const notifications = reactive({}); export const notifications = reactive({});
export const addNotification = (id, title, message, type, lifeTime = 0) => { export const addNotification = (id, title, message, type, lifeTime = 3000) => {
notifications[id] = { notifications[id] = {
title, title,
message, message,

View File

@@ -10,64 +10,13 @@
text-color="blue", text-color="blue",
size="xl" size="xl"
) )
q-btn(
v-if="!editMode",
style="width: 48px; height: 48px",
id="edit",
rounded,
padding="0px",
icon="edit",
text-color="blue",
size="xl",
@click="toggleEditMode",
)
q-btn(
v-if="editMode",
style="width: 48px; height: 48px",
id="edit",
rounded,
padding="0px",
icon="check",
text-color="blue",
size="xl"
@click="save",
)
q-btn(
v-if="editMode",
style="width: 48px; height: 48px",
id="edit",
rounded,
padding="0px",
icon="cancel",
text-color="blue",
size="xl",
@click="cancel",
)
</template> </template>
<script> <script>
import BaseButtonSidebar from "@/components/base/BaseSidebarButton";
export default { export default {
name: "TheRightMenu", name: "TheRightMenu",
emits: ["save", "cancel", "update:editMode"], components: { BaseButtonSidebar },
data() {
return {
editMode: false,
};
},
methods: {
toggleEditMode() {
this.editMode = !this.editMode;
this.$emit("update:editMode", this.editMode);
},
save() {
this.toggleEditMode();
this.$emit("save");
},
cancel() {
this.toggleEditMode();
this.$emit("cancel");
},
},
}; };
</script> </script>

View File

@@ -1,22 +1,22 @@
<template lang="pug"> <template lang="pug">
.sidebar.flex.flex-col.justify-between.py-4.rounded.items-center .sidebar.flex.flex-col.justify-between.pt-4.px-2.pb-7.rounded
.flex.flex-col.gap-y-4 .flex.flex-col.gap-y-4
base-button-sidebar( base-button-sidebar(
v-for="button in pageSettings.filter((el) => el.id !== 'settings')", v-for="button in pageSettings.filter((el) => el.id !== 'settings')",
:path="button.path", :path="button.path",
:id="button.id", :id="button.id",
:active="button.active", :active="button.active",
:change-style-page="changeStylePage", :change-style-page="changeStylePage",
:icon="button.icon" :icon="button.icon"
) )
.flex.text-4xl.flex-col.gap-y-6 .flex.text-4xl.flex-col.gap-y-6
base-button-sidebar( base-button-sidebar(
:path="getSettings.path", :path="getSettings.path",
:id="getSettings.id", :id="getSettings.id",
:active="getSettings.active", :active="getSettings.active",
:change-style-page="changeStylePage", :change-style-page="changeStylePage",
:icon="getSettings.icon" :icon="getSettings.icon"
) )
</template> </template>
<script> <script>

View File

@@ -1,55 +0,0 @@
<template lang="pug">
.flex
template(v-if="value")
q-btn(
@click="toggleEditMode",
:style="{backgroundColor: 'var(--bg-light-grey)', color: 'blue'}",
padding="10px",
)
q-icon(
name="edit",
size="20px",
)
template(v-else)
q-btn.mr-2(
@click="save",
:style="{backgroundColor: 'var(--bg-light-grey)', color: 'blue'}",
padding="10px",
)
q-icon(
name="check",
size="20px",
)
q-btn(
@click="cancel",
:style="{backgroundColor: 'var(--bg-light-grey)', color: 'blue'}",
padding="10px",
)
q-icon(
name="cancel",
size="20px",
)
</template>
<script>
import { v_model } from "@/shared/mixins/v-model";
export default {
name: "BaseEditModeSwitcher",
mixins: [v_model],
emits: ["save", "cancel"],
methods: {
toggleEditMode() {
this.value = !this.value;
},
save() {
this.toggleEditMode();
this.$emit("save");
},
cancel() {
this.toggleEditMode();
this.$emit("cancel");
},
},
};
</script>

View File

@@ -1,58 +0,0 @@
<template lang="pug">
.XXX.p-2
template(v-if="!editMode")
template(v-if="value.length")
template(v-for="elem in value", :key="elem.id")
.text-m.pb-1 {{ elem.label }}
.font-medium.text-xl.pb-3 {{ elem.value }}
.flex.flex-col.justify-evenly.items-center.content-center(v-else, style="height:100px")
q-icon(
name="help_outline",
size="40px",
class="icon",
id="empty"
)
.text {{ emptyMessage }}
template(v-else)
template(v-for="elem in value", :key="elem.id")
.text-m.pb-1 {{ elem.label }}
base-input.pb-3(
v-model="elem.value",
size="M",
)
.flex.justify-center
q-btn-dropdown(label="Добавить", icon="add", outline, color="blue-6")
q-list(v-for="option in options", :key="option")
q-item(clickable, v-close-popup, @click="addOption(option)")
q-item-label {{ option }}
</template>
<script>
import BaseInput from "@/components/base/BaseInput.vue";
import { v_model } from "@/shared/mixins/v-model";
export default {
name: "BaseFieldList",
components: { BaseInput },
mixins: [v_model],
props: {
editMode: Boolean,
options: Array,
emptyMessage: String,
},
data() {
return {};
},
methods: {
addOption(option) {
this.value.push({ label: option, value: "" });
},
},
};
</script>
<style lang="sass" scoped>
.XXX
min-height: 100px
</style>

View File

@@ -23,7 +23,7 @@
:debounce="debounce", :debounce="debounce",
:shadow-text="shadowText", :shadow-text="shadowText",
:autofocus="autofocus", :autofocus="autofocus",
hide-bottom-space hide-bottom-space,
:error="error", :error="error",
@focus="e => $emit('focus', e)" @focus="e => $emit('focus', e)"
) )
@@ -86,6 +86,7 @@ export default {
circle: Boolean, circle: Boolean,
height: String, height: String,
error: Boolean, error: Boolean,
hint: String,
}, },
emits: ["update:modelValue", "focus"], emits: ["update:modelValue", "focus"],
computed: { computed: {

View File

@@ -21,16 +21,25 @@
@filter="filterFn", @filter="filterFn",
:popup-content-style="popupContentStyle" :popup-content-style="popupContentStyle"
@blur="$emit('blur')" @blur="$emit('blur')"
:multiple="multiple"
) )
template(#selected) template(#selected, v-if="!multiple")
slot(name="selected") slot(name="selected")
template(v-slot:option="scope", v-if="!customOption") template(v-slot:option="{itemProps, opt}", v-if="!customOption")
q-item(v-bind="scope.itemProps", style="justify-content: center", v-if="value?.icon") q-item(v-bind="itemProps", style="justify-content: center", v-if="value?.icon")
q-item-section(avatar, style="padding: 0px; min-width: 0px") q-item-section(avatar, style="padding: 0px; min-width: 0px")
q-icon.icon(:name="scope.opt.icon", size="24px") q-icon.icon(:name="opt.icon", size="24px")
q-item.item.px-4.py-2(v-bind="scope.itemProps", style="justify-content: center", v-else) q-item.item.px-4.py-2.multiple(
:class="{ 'selected': value?.length && ~value?.lastIndexOf(opt.value) }"
v-bind="itemProps",
style="justify-content: center",
v-else-if="multiple"
)
q-item-section q-item-section
q-item-label.text-dark.text-base.font-medium {{ scope.opt.label }} q-item-label.text-dark.text-base.font-medium {{ opt.label }}
q-item.item.px-4.py-2(v-bind="itemProps", style="justify-content: center", v-else)
q-item-section
q-item-label.text-dark.text-base.font-medium {{ opt.label }}
template(v-slot:option="{itemProps, opt}", v-else) template(v-slot:option="{itemProps, opt}", v-else)
slot( slot(
name="customOption", name="customOption",
@@ -79,6 +88,7 @@ export default {
customOption: Boolean, customOption: Boolean,
filterFn: Function, filterFn: Function,
popupContentStyle: Object, popupContentStyle: Object,
multiple: Boolean,
}, },
emits: ["update:modelValue", "blur"], emits: ["update:modelValue", "blur"],
computed: { computed: {
@@ -248,8 +258,14 @@ export default {
border-bottom: 1px solid var(--gray-secondary) border-bottom: 1px solid var(--gray-secondary)
&:last-child &:last-child
border-bottom: none border-bottom: none
&.multiple.selected
background-color: var(--gray-thirdly)
.text-dark
color: var(--q-primary) !important
&:hover &:hover
background-color: var(--gray-thirdly) background-color: var(--gray-thirdly)
.q-focus-helper
display: none !important
.q-menu .q-menu
box-shadow: 1px 1px 8px 0px rgba(37, 40, 80, 0.15) !important box-shadow: 1px 1px 8px 0px rgba(37, 40, 80, 0.15) !important

View File

@@ -5,11 +5,28 @@
.text.pt-3.cursor-pointer(@click="gotoCalendar") Расписание приемов .text.pt-3.cursor-pointer(@click="gotoCalendar") Расписание приемов
.text.pt-3.cursor-pointer(@click="gotoMedicalCards") Медицинские карты .text.pt-3.cursor-pointer(@click="gotoMedicalCards") Медицинские карты
base-select(:items="options", v-model="multiple", size="M", multiple)
.res {{multiple}}
</template> </template>
<script> <script>
import BaseSelect from "@/components/base/BaseSelect.vue";
export default { export default {
name: "TheHome", name: "TheHome",
components: {
BaseSelect,
},
data() {
return {
options: ["Google", "Facebook", "Twitter", "Apple", "Oracle"].map(
(e) => ({ label: e, value: e + "v" })
),
multiple: null,
};
},
methods: { methods: {
gotoCalendar() { gotoCalendar() {
this.$router.push("/calendar"); this.$router.push("/calendar");

View File

@@ -53,14 +53,9 @@ export default {
...mapActions({ ...mapActions({
getMedicalCardData: "getMedicalCardDataByPersonId", getMedicalCardData: "getMedicalCardDataByPersonId",
createMedicalCard: "createMedicalCard", createMedicalCard: "createMedicalCard",
getPerson: "GET_PERSON",
}), }),
openMedicalCard() { openMedicalCard() {
this.getPerson(this.person.id) this.$router.push("medical-card/" + this.person["medical_card_id"]);
.then(() =>
this.$router.push("medical-card/" + this.person["medical_card_id"])
)
.catch((error) => alert(error));
}, },
createMedCard() { createMedCard() {
this.createMedicalCard({ this.createMedicalCard({

View File

@@ -3,8 +3,8 @@
header-record-form( header-record-form(
:current-status="currentStatus", :current-status="currentStatus",
:statuses="patientData.statuses", :statuses="patientData.statuses",
:choice-status="choiceStatus" :choice-status="choiceStatus",
v-model="time" v-model="time",
) )
base-input-with-search(v-model="patient", @create-person="createPerson") base-input-with-search(v-model="patient", @create-person="createPerson")
.flex.flex-col.flex-auto.l.gap-y-8 .flex.flex-col.flex-auto.l.gap-y-8
@@ -47,6 +47,8 @@ import { fetchWrapper } from "@/shared/fetchWrapper";
import { mapActions } from "vuex"; import { mapActions } from "vuex";
import PatientCreationForm from "@/components/PatientCreationForm.vue"; import PatientCreationForm from "@/components/PatientCreationForm.vue";
import BaseModal from "@/components/base/BaseModal.vue"; import BaseModal from "@/components/base/BaseModal.vue";
import { addNotification } from "@/components/Notifications/notificationContext";
import { errors } from "@/shared/errors";
export default { export default {
name: "CreateEventForm", name: "CreateEventForm",
@@ -70,6 +72,7 @@ export default {
patientData: patientData, patientData: patientData,
currentStatus: patientData.statuses.find((e) => e.name === "Не принят"), currentStatus: patientData.statuses.find((e) => e.name === "Не принят"),
showCreateModal: false, showCreateModal: false,
errors: {},
}; };
}, },
computed: { computed: {
@@ -106,6 +109,10 @@ export default {
await this.getEvents(); await this.getEvents();
this.closeForm(); this.closeForm();
} }
if (event?.code) {
this.errors = event?.fields;
addNotification(new Date(), "Ошибка", errors[event?.type], "error");
}
}, },
}, },
mounted() {}, mounted() {},

View File

@@ -35,7 +35,7 @@
.flex.gap-x-3.items-center .flex.gap-x-3.items-center
.text.font-semibold Время: .text.font-semibold Время:
.flex.gap-x-1 .flex.gap-x-1
base-input.input.no-border(size="XS", mask="##:## - ##:##", v-model="times" ) base-input.input.no-border(size="XS", mask="##:## - ##:##", v-model="times", :error="!!errors?.['start'] || !!errors?.['end']")
.flex.h-14.gap-x-3.items-center.text-smm .flex.h-14.gap-x-3.items-center.text-smm
.text.font-semibold Медкарта: .text.font-semibold Медкарта:
.flex.gap-x-1 .flex.gap-x-1
@@ -86,7 +86,12 @@ export default {
MedcardModal, MedcardModal,
}, },
mixins: [v_model], mixins: [v_model],
props: { currentStatus: Object, statuses: Array, choiceStatus: Function }, props: {
currentStatus: Object,
statuses: Array,
choiceStatus: Function,
errors: Object,
},
data() { data() {
return { return {
noname, noname,

View File

@@ -1,11 +1,10 @@
<template lang="pug"> <template lang="pug">
.w-full.fit.row.no-wrap.justify-evenly .w-full.fit.row.no-wrap.justify-evenly
.col-grow.pr-2 .col-grow.pr-2
medical-card-patient-form(:edit-mode="editMode", :need-save="needSave") medical-card-patient-form
medical-card-tabs(v-model="currentMenuItem") medical-base-info.mt-2
medical-card-general-info(:edit-mode="editMode").mt-2
.h-full .h-full
the-right-menu(v-model:edit-mode="editMode") the-right-menu
</template> </template>
<script> <script>
@@ -17,9 +16,6 @@ import MedicalRemoveModal from "@/pages/newMedicalCard/components/MedicalRemoveM
import { mapState } from "vuex"; import { mapState } from "vuex";
import TheRightMenu from "@/components/TheRightMenu.vue"; import TheRightMenu from "@/components/TheRightMenu.vue";
import MedicalCardPatientForm from "@/pages/newMedicalCard/components/MedicalCardPatientForm.vue"; import MedicalCardPatientForm from "@/pages/newMedicalCard/components/MedicalCardPatientForm.vue";
import MedicalCardTabs from "@/pages/newMedicalCard/components/MedicalCardTabs.vue";
import MedicalCardGeneralInfo from "@/pages/newMedicalCard/tabs/general/MedicalCardGeneralInfo.vue";
export default { export default {
name: "TheMedicalCard", name: "TheMedicalCard",
@@ -31,14 +27,11 @@ export default {
MedicalRemoveModal, MedicalRemoveModal,
TheRightMenu, TheRightMenu,
MedicalCardPatientForm, MedicalCardPatientForm,
MedicalCardTabs,
MedicalCardGeneralInfo,
}, },
data() { data() {
return { return {
currentMenuItem: "MedicalBaseInfo", currentMenuItem: "MedicalBaseInfo",
isShownRemoveModal: false, isShownRemoveModal: false,
editMode: false,
}; };
}, },
methods: { methods: {

View File

@@ -15,7 +15,13 @@
.flex.w-full.items-center.gap-4(v-for="field in data.fields") .flex.w-full.items-center.gap-4(v-for="field in data.fields")
.label-field.font-sm.text-sm.whitespace-nowrap {{`${field.label} :`}} .label-field.font-sm.text-sm.whitespace-nowrap {{`${field.label} :`}}
.flex.gap-3.items-center.h-10(v-if="field.type === 'avatar'") .flex.gap-3.items-center.h-10(v-if="field.type === 'avatar'")
base-avatar(:size="40")
img.avatar.object-cover(
v-if="basic[data.dataKey][field.key]?.photo"
:src="basic[data.dataKey][field.key].photo"
alt="AV"
)
span(v-else) {{ avatar }}
.replace-photo.font-medium.text-base.cursor-pointer(v-if="isEdit" @click="showModal = true") Заменить фото .replace-photo.font-medium.text-base.cursor-pointer(v-if="isEdit" @click="showModal = true") Заменить фото
base-modal(v-model="showModal" title="Загрузить изображение") base-modal(v-model="showModal" title="Загрузить изображение")
base-upload-photo( base-upload-photo(
@@ -58,7 +64,7 @@
<script> <script>
import BaseSelect from "@/components/base/BaseSelect.vue"; import BaseSelect from "@/components/base/BaseSelect.vue";
import BaseAvatar from "@/components/base/BaseAvatar.vue";
import BaseModal from "@/components/base/BaseModal.vue"; import BaseModal from "@/components/base/BaseModal.vue";
import BaseUploadPhoto from "@/components/base/BaseUploadPhoto.vue"; import BaseUploadPhoto from "@/components/base/BaseUploadPhoto.vue";
import MedicalFormWrapper from "@/pages/newMedicalCard/components/MedicalFormWrapper.vue"; import MedicalFormWrapper from "@/pages/newMedicalCard/components/MedicalFormWrapper.vue";
@@ -82,7 +88,7 @@ export default {
components: { components: {
MedicalFormWrapper, MedicalFormWrapper,
BaseInput, BaseInput,
BaseAvatar,
BaseSelect, BaseSelect,
BaseModal, BaseModal,
BaseUploadPhoto, BaseUploadPhoto,
@@ -121,11 +127,11 @@ export default {
updateBasicData() { updateBasicData() {
const photoFormData = new FormData(); const photoFormData = new FormData();
if ( if (
this.basic.patient.photo.file && this.basic.personalData.photo.file &&
this.basic.patient.photo.photo !== this.basic.personalData.photo.photo !==
this.initDataBasic.patient.photo.photo this.initDataBasic.personalData.photo.photo
) { ) {
photoFormData.append("photo", this.basic.patient?.photo?.file[0]); photoFormData.append("photo", this.basic.personalData?.photo?.file[0]);
console.log("photo_update"); console.log("photo_update");
} }
Object.keys(this.basic).map((key) => { Object.keys(this.basic).map((key) => {
@@ -150,7 +156,7 @@ export default {
if (Object.keys(removeEmptyFields(updateData)).length) { if (Object.keys(removeEmptyFields(updateData)).length) {
if (!isInitDataEmpty) if (!isInitDataEmpty)
return this.createAddress({ return this.createAddress({
id: this.basic.patient.id, id: this.basic.personalData.id,
address: updateData, address: updateData,
category: key, category: key,
}); });

View File

@@ -8,8 +8,12 @@
:open-edit="openEdit", :open-edit="openEdit",
:save="saveChange" :save="saveChange"
) )
q-form.form-wrap.gap-6.w-full(ref="documentForm", :no-error-focus="true") q-form.form-wrap.gap-x-6.w-full(ref="documentForm", :no-error-focus="true")
.data-section.flex.flex-col.gap-2(v-for="data in configData", :key="data.dataLabel") .data-section.flex.flex-col.gap-2(
v-for="data in configData",
:key="data.dataLabel",
:class="data?.dataClass"
)
.font-semibold.text-sm.whitespace-nowrap {{data.dataLabel}} .font-semibold.text-sm.whitespace-nowrap {{data.dataLabel}}
.flex.w-full.items-center.gap-4( .flex.w-full.items-center.gap-4(
v-for="field in data.fields", v-for="field in data.fields",
@@ -66,7 +70,9 @@
:rule="[(val) => !personDataField.includes(data.dataKey + ':' + field.key) ? checkPassportFields(val, field.rules) : field.rules(val, initialDocData?.[data.dataKey]?.id)]", :rule="[(val) => !personDataField.includes(data.dataKey + ':' + field.key) ? checkPassportFields(val, field.rules) : field.rules(val, initialDocData?.[data.dataKey]?.id)]",
size="M" size="M"
) )
.icon-copy.my-auto.text-lg.label-field.cursor-pointer( q-icon.my-auto.cursor-pointer.copy(
size="20px",
name="app:copy",
v-if="checkCopiedFields(field.key) && !!docData[data.dataKey][field.key]", v-if="checkCopiedFields(field.key) && !!docData[data.dataKey][field.key]",
@click="copyValue(docData[data.dataKey][field.key])" @click="copyValue(docData[data.dataKey][field.key])"
) )
@@ -314,10 +320,11 @@ export default {
.form-wrap .form-wrap
display: grid display: grid
grid-template-columns: repeat(3, 1fr) grid-template-columns: repeat(3, 1fr)
grid-template-rows: repeat(1, 1fr) row-gap: 32px
grid-template-rows: 1.2fr 1.8fr
@media(max-width: 1440px) @media(max-width: 1440px)
grid-template-columns: repeat(2, 1fr) grid-template-columns: repeat(2, 1fr)
grid-template-rows: 3fr 1fr grid-template-rows: 1.2fr 1.8fr
.label-field .label-field
min-width: 110px min-width: 110px
color: var(--font-grey-color) color: var(--font-grey-color)
@@ -336,4 +343,15 @@ export default {
background-color: var(--btn-red-color) background-color: var(--btn-red-color)
.cancel-icon :deep(path) .cancel-icon :deep(path)
fill: white fill: white
.copy :deep(path)
fill: var(--font-grey-color)
.passport
grid-column: 1 / 2
grid-row: 1 / 3
.insurance
grid-column: 2 / 3
grid-row: 1 / 2
.tax
grid-column: 2 / 3
grid-row: 2 / 3
</style> </style>

View File

@@ -18,9 +18,8 @@
:style="{marginTop: field.label === 'Документы' ? '20px' : null}" :style="{marginTop: field.label === 'Документы' ? '20px' : null}"
) )
.label-field.font-sm.text-sm.whitespace-nowrap {{`${field.label} :`}} .label-field.font-sm.text-sm.whitespace-nowrap {{`${field.label} :`}}
.flex.gap-3.items-center.h-10.w-10( .flex.gap-3.items-center.h-10(
v-if="field.type === 'photo'", v-if="field.type === 'photo'",
:style="{'min-width': field.label === 'Документы' ? '324px' : '302px'}"
) )
.flex.w-10.h-10.relative(v-if="insuranceData[insurance.insuranceKey]?.attachments?.photo") .flex.w-10.h-10.relative(v-if="insuranceData[insurance.insuranceKey]?.attachments?.photo")
img.rounded.avatar.object-cover( img.rounded.avatar.object-cover(
@@ -90,8 +89,8 @@
:name="insurance.insuranceKey + ':' + field.key", :name="insurance.insuranceKey + ':' + field.key",
:rule="[(val) => checkFields(val, insurance.insuranceKey, field.rules)]" :rule="[(val) => checkFields(val, insurance.insuranceKey, field.rules)]"
) )
q-icon.my-auto.text-lg.label-field.cursor-pointer.copy( q-icon.my-auto.cursor-pointer.copy(
size="18px", size="20px",
name="app:copy", name="app:copy",
v-if="checkCopiedFields(field.key, insuranceData[insurance.insuranceKey])", v-if="checkCopiedFields(field.key, insuranceData[insurance.insuranceKey])",
@click="copyValue(insuranceData[insurance.insuranceKey][field.key])" @click="copyValue(insuranceData[insurance.insuranceKey][field.key])"

View File

@@ -10,8 +10,8 @@
</template> </template>
<script> <script>
// import MedicalSidebar from "@/pages/newMedicalCard/components/MedicalSidebar.vue"; import MedicalSidebar from "@/pages/newMedicalCard/components/MedicalSidebar.vue";
// import MedicalBasicDataWrapper from "@/pages/newMedicalCard/components/MedicalBasicDataWrapper.vue"; import MedicalBasicDataWrapper from "@/pages/newMedicalCard/components/MedicalBasicDataWrapper.vue";
import MedicalAllergiesWrapper from "@/pages/newMedicalCard/components/MedicalAllergiesWrapper.vue"; import MedicalAllergiesWrapper from "@/pages/newMedicalCard/components/MedicalAllergiesWrapper.vue";
import MedicalConfidantWrapper from "@/pages/newMedicalCard/components/MedicalConfidantWrapper.vue"; import MedicalConfidantWrapper from "@/pages/newMedicalCard/components/MedicalConfidantWrapper.vue";
import MedicalHealthStateWrapper from "@/pages/newMedicalCard/components/MedicalHealthStateWrapper.vue"; import MedicalHealthStateWrapper from "@/pages/newMedicalCard/components/MedicalHealthStateWrapper.vue";
@@ -20,8 +20,8 @@ import MedicalDentalFormulasWrapper from "@/pages/newMedicalCard/components/Medi
export default { export default {
name: "MedicalBaseInfo", name: "MedicalBaseInfo",
components: { components: {
// MedicalSidebar, MedicalSidebar,
// MedicalBasicDataWrapper, MedicalBasicDataWrapper,
MedicalAllergiesWrapper, MedicalAllergiesWrapper,
MedicalConfidantWrapper, MedicalConfidantWrapper,
MedicalHealthStateWrapper, MedicalHealthStateWrapper,

View File

@@ -0,0 +1,28 @@
<template lang="pug">
.flex.flex-col.gap-y-2.wrapper.h-full.w-full
basic-data-form
contacts-form
documents-form
insurance-form
</template>
<script>
import BasicDataForm from "@/pages/newMedicalCard/components/BasicDataForms/BasicDataForm.vue";
import DocumentsForm from "@/pages/newMedicalCard/components/BasicDataForms/DocumentsForm.vue";
import InsuranceForm from "@/pages/newMedicalCard/components/BasicDataForms/InsuranceForm.vue";
import ContactsForm from "@/pages/newMedicalCard/components/BasicDataForms/ContactsForm.vue";
export default {
name: "MedicalBasicDataWrapper",
components: { BasicDataForm, DocumentsForm, InsuranceForm, ContactsForm },
};
</script>
<style lang="sass" scoped>
.wrapper
flex: 1
overflow-y: auto
min-width: 560px
min-height: 350px
@media (max-width: 600px)
width: fit-content
</style>

View File

@@ -1,6 +1,6 @@
<template lang="pug"> <template lang="pug">
.patient-info.pt-4.flex.flex-col.rounded.font-medium.text-m .patient-info.pt-4.pr-6.flex.flex-col.justify-between.rounded.font-medium.text-m
.ml-4.flex.justify-between .ml-6.flex.justify-between
//- TODO по клику переход на предыдущую страницу //- TODO по клику переход на предыдущую страницу
q-breadcrumbs q-breadcrumbs
template(v-slot:separator) template(v-slot:separator)
@@ -12,91 +12,107 @@
q-breadcrumbs-el(:label="routes[routingHistory.state.back]") q-breadcrumbs-el(:label="routes[routingHistory.state.back]")
q-breadcrumbs-el(:label="`Медкарта #${medicalCardData?.number}`") q-breadcrumbs-el(:label="`Медкарта #${medicalCardData?.number}`")
.mr-4.flex q-btn(
special-marks(:marks="patient?.marks", :edit-mode="editMode") @click="toggleEditMode",
:style="{backgroundColor: 'var(--bg-light-grey)', color: 'blue'}",
padding="10px",
.ml-6.flex.gap-x-4.col-grow.items-center )
q-icon(
name="edit",
size="20px",
)
.ml-6.flex.gap-x-4
q-avatar(size="80px", :style="{'background-color': 'var(--border-light-grey-color)', color: 'var(--font-dark-blue-color)'}") q-avatar(size="80px", :style="{'background-color': 'var(--border-light-grey-color)', color: 'var(--font-dark-blue-color)'}")
img(v-if="patientData?.photo", :src="url + patientData?.photo") img(v-if="patientData?.photo", :src="url + patientData?.photo")
span.text-2xl(v-else) {{patientAvatar}} span.text-2xl(v-else) {{patientAvatar}}
.flex.gap-y-1.flex-col(v-if="!editMode") template(v-if="!editMode")
.flex.items-center .flex.gap-y-1.flex-col
span.text-xxl.font-bold.mr-3( .flex.items-center
:style="{color: 'var(--font-dark-blue-color)', 'line-height': '135%'}", span.text-xxl.font-bold.mr-3(
) {{ patientFullName }} :style="{color: 'var(--font-dark-blue-color)', 'line-height': '135%'}",
) {{patientName}}
span.date-color {{ patient?.gender }}
span.date-color {{ patient?.birthDate }}
.flex-col(v-else) span.text-smm.label-color Добавлен в систему:
.flex.gap-x-2 span.date-color {{ patientData?.gender }}
base-input( span.date-color {{ patientData?.birth_date }}
v-model="patientEdit.lastName" span.text-smm.label-color Спец отметки:
size="S",
label="Фамилия" template(v-else)
) base-input(
base-input( v-model="fullname"
v-model="patientEdit.firstName" size="M",
size="S", label="Фамилия"
label="Имя" )
) base-input(
base-input( v-model="fullname"
v-model="patientEdit.patronymic" size="M",
size="S", label="Имя"
label="Отчество" )
) base-input(
.flex.gap-x-2 v-model="fullname"
base-input( size="M",
v-model="patientEdit.birthDate" label="Отчество"
size="S", )
label="Дата рождения"
) .flex.justify-between.pt-4
base-input( .flex
v-model="patientEdit.gender" .menu-item.px-6.py-10px.cursor-pointer.text-base.whitespace-nowrap(
size="S", v-for="item in menuItem",
label="Пол" @click="selectItem(item)",
) :class="{'menu-item-active': item.component === modelValue}",
:key="item.id",
:id="item.id"
) {{item.title}}
</template> </template>
<script> <script>
import BaseInput from "@/components/base/BaseInput.vue"; import BaseInput from "@/components/base/BaseInput.vue";
import { routesDictionary } from "@/pages/newMedicalCard/utils/medicalConfig.js"; import { v_model } from "@/shared/mixins/v-model";
import SpecialMarks from "@/pages/newMedicalCard/components/SpecialMarks.vue"; import { column } from "@/pages/clients/utils/tableConfig.js";
import * as moment from "moment/moment";
import {
headerMenuItem,
routesDictionary,
} from "@/pages/newMedicalCard/utils/medicalConfig.js";
export default { export default {
name: "MedicalCardPatientForm", name: "MedicalCardPatientForm",
props: { mixins: [v_model],
editMode: Boolean,
},
data() { data() {
return { return {
menuItem: headerMenuItem,
routes: routesDictionary, routes: routesDictionary,
editMode: true,
fullname: "", fullname: "",
patientEdit: {},
patientData: null,
}; };
}, },
components: { components: {
BaseInput, BaseInput,
SpecialMarks,
}, },
computed: { computed: {
patient() {
return this.$store.getters.PERSON;
},
routingHistory() { routingHistory() {
return this.$store.state.routingHistory; return this.$store.state.routingHistory;
}, },
url() { url() {
return this.$store.state.url; return this.$store.state.url;
}, },
medicalCardData() {
patientFullName() { return this.$store.state.medical.medicalCard;
return `${this.patient.lastName} ${this.patient.firstName} ${this.patient.patronymic}`; },
patientData() {
return this.medicalCardData?.person;
},
patientName() {
let name = {
lastName: this.ckeckName("last_name"),
firstName: this.ckeckName("first_name"),
patronymic: this.ckeckName("patronymic"),
};
return `${name.lastName} ${name.firstName} ${name.patronymic}`;
}, },
patientAvatar() { patientAvatar() {
let checkedFirstName = let checkedFirstName =
this.patientData?.first_name !== null this.patientData?.first_name !== null
@@ -104,13 +120,38 @@ export default {
: this.patientData?.last_name[1]; : this.patientData?.last_name[1];
return `${this.patientData?.last_name[0]}${checkedFirstName}`; return `${this.patientData?.last_name[0]}${checkedFirstName}`;
}, },
priority() {
return column
.find((elem) => elem.name === "priority")
?.settings.find((elem) => elem.priority === this.patientData?.priority);
},
createdDate() {
return this.checkDate("created_at");
},
updatedDate() {
return this.checkDate("updated_at");
},
allergiesList() {
return this.patientData?.allergic?.map((elem) => ({
name: elem?.name,
title: elem?.title,
}));
},
}, },
watch: { methods: {
editMode: { selectItem(item) {
immediate: true, this.value = item?.component;
handler(val) { },
val && (this.patientEdit = { ...this.patient }); ckeckName(field) {
}, return this.patientData?.[field] ? this.patientData[field] : "";
},
checkDate(field) {
return this.medicalCardData?.[field]
? moment.parseZone(this.medicalCardData?.[field]).format("DD.MM.YYYY")
: "";
},
toggleEditMode() {
this.editMode = !this.editMode;
}, },
}, },
}; };
@@ -119,11 +160,47 @@ export default {
.patient-info .patient-info
background-color: var(--default-white) background-color: var(--default-white)
min-height: 190px min-height: 190px
.menu-item
color: var(--font-grey-color)
border-bottom: 1px solid transparent
.menu-item:first-child
border-bottom-left-radius: 4px
.menu-item-active
color: var(--font-dark-blue-color)
border-bottom: 1px solid var(--font-dark-blue-color)
.q-btn
font-weight: 500 !important
.q-btn :deep(.on-left)
margin-right: 4px !important
.q-btn :deep(.q-btn-dropdown__arrow)
margin-left: 0px !important
opacity: 0.7
.text-grey
color: var(--font-grey-color) !important
.q-breadcrumbs__el .q-breadcrumbs__el
font-size: 12px font-size: 12px
line-height: 135% line-height: 135%
color: var(--font-grey-color) color: var(--font-grey-color)
.q-breadcrumbs :deep(.q-breadcrumbs__separator) .q-breadcrumbs :deep(.q-breadcrumbs__separator)
margin: 10px 0 4px 8px !important margin: 10px 0 4px 8px !important
.label-color
color: var(--font-grey-color)
.date-color
color: var(--font-dark-blue-color)
.allergies-list
max-height: 190px
overflow-y: auto
&::-webkit-scrollbar
width: 4px
background-color: var(--default-white)
&::-webkit-scrollbar-track
margin: 0
.allergies-item
color: var(--font-dark-blue-color)
.allergies-item:hover
background-color: var(--bg-light-grey)
.icon-eye
display: block
.icon-eye
display: none
</style> </style>

View File

@@ -1,42 +0,0 @@
<template lang="pug">
.flex.justify-between.bg-white
.flex
.text {{ headerMenuItem }}
.menu-item.px-6.py-10px.cursor-pointer.text-base.whitespace-nowrap(
v-for="item in menuItem",
@click="selectItem(item)",
:class="{'menu-item-active': item.component === modelValue}",
:key="item.id",
:id="item.id"
) {{item.title}}
</template>
<script>
import { headerMenuItem } from "@/pages/newMedicalCard/utils/medicalConfig.js";
import { v_model } from "@/shared/mixins/v-model";
export default {
name: "MedicalCardTabs",
mixins: [v_model],
data() {
return {
menuItem: headerMenuItem,
};
},
methods: {
selectItem(item) {
this.value = item?.component;
},
},
};
</script>
<style lang="sass">
.menu-item
color: var(--font-grey-color)
border-bottom: 1px solid transparent
.menu-item:first-child
border-bottom-left-radius: 4px
.menu-item-active
color: var(--font-dark-blue-color)
border-bottom: 1px solid var(--font-dark-blue-color)
</style>

View File

@@ -1,91 +0,0 @@
<template lang="pug">
template(v-if="!editMode")
.flex(v-for="specialMark in specialMarks")
.flex.special-mark.rounded.mr-2.items-center.justify-center.cursor-pointer(
:style="{'background':specialMark.bgColor}"
)
q-icon(
size="24px",
:name="specialMark.icon",
:color="specialMark.color",
)
q-tooltip(class="bg-blue" :offset="[10, 10]")
.text-m {{ specialMark.description }}
template(v-else)
.flex(v-for="specialMark in editSpecialMarks")
.flex.special-mark.edit-mode.rounded.mr-2.items-center.justify-center.cursor-pointer(
:style="{'background':specialMark.bgColor}"
@click="toggleMark(specialMark.name)"
)
q-icon(
size="24px",
:name="specialMark.icon",
:color="specialMark.color",
)
q-tooltip(class="bg-blue" :offset="[10, 10]")
.text-m {{ specialMark.description }}
</template>
<script>
import { specialMarksIconsConfig } from "@/pages/newMedicalCard/utils/medicalConfig.js";
export default {
name: "SpecialMarks",
props: {
marks: Array,
editMode: Boolean,
},
data() {
return {
editSpecialMarks: [],
};
},
computed: {
specialMarks() {
return this.marks.map(
(el) =>
specialMarksIconsConfig[el] || specialMarksIconsConfig["unknown"]
);
},
},
watch: {
editMode: {
immediate: true,
handler(val) {
if (!val) return;
this.editSpecialMarks = [];
for (let mark in specialMarksIconsConfig) {
if (mark === "unknown") continue;
let t = {
...specialMarksIconsConfig[mark],
name: mark,
};
if (this.marks.indexOf(mark) === -1) {
t.color = "blue-grey-3";
t.bgColor = "#eceff1";
}
this.editSpecialMarks.push({ ...t });
}
},
},
},
methods: {
toggleMark(value) {
alert(value);
},
},
};
</script>
<style lang="sass">
.special-mark
height: 32px
width: 32px
.special-mark.edit-mode
&:hover
border: 2px solid black
</style>

View File

@@ -1,79 +0,0 @@
<template lang="pug">
.bg-white.p-4.rounded.fit.row.wrap.justify-start.items-start.content-start
.col-4
template(v-if="!editMode")
template(v-for="contact in contacts")
.font-medium.text-xl.pb-1 {{ contact.category }}
.text-m.pb-3 {{ contact.value }}
template(v-else)
template(v-for="contact in contacts")
base-input(
v-model="x",
size="S",
:label="contact.category"
)
q-icon(
name="add",
size="20px",
class="icon",
id="add"
)
.col
template(v-if="!editMode")
template(v-for="address in addresses")
.font-medium.text-xl.pb-1 {{ address.category }}
.text-m.pb-3 {{ address.full_address }}
template(v-else)
template(v-for="address in addresses")
base-input(
v-model="x",
size="S",
:label="address.category"
)
q-icon(
name="add",
size="20px",
class="icon",
id="add"
)
</template>
<script>
import BaseInput from "@/components/base/BaseInput.vue";
export default {
name: "GeneralInfo",
props: {
editMode: Boolean,
},
components: {
BaseInput,
},
data() {
return {
x: "",
};
},
computed: {
addresses: {
get() {
return this.$store.getters.ADDRESSES;
},
set(value) {
value;
},
},
contacts: {
get() {
return this.$store.getters.CONTACTS;
},
set(value) {
value;
},
},
},
};
</script>
<style lang="sass">
.cont
</style>

View File

@@ -1,63 +0,0 @@
<template lang="pug">
.bg-white.p-4.rounded.fit.row.wrap.justify-start.items-start.content-start
.col-3
base-field-list.mt-2(
:editMode="editMode",
:options="contactInfo.options",
:emptyMessage="contactInfo.emptyMessage",
v-model="contacts"
)
.col-5
base-field-list.mt-2(
:editMode="editMode",
:options="addressInfo.options",
:emptyMessage="addressInfo.emptyMessage",
v-model="addresses"
)
</template>
<script>
import BaseInput from "@/components/base/BaseInput.vue";
import BaseFieldList from "@/components/base/BaseFieldList";
import { contactInformation, addressInformation } from "./generalInfoData";
export default {
name: "GeneralInfo",
props: {
editMode: Boolean,
},
components: {
BaseInput,
BaseFieldList,
},
data() {
return {
x: "",
contactInfo: contactInformation,
addressInfo: addressInformation,
contacts: [
{
id: "1",
label: "E-mail",
value: "dol@yandex.ru",
type: "email",
},
{
id: "2",
label: "Контактный телефон",
value: "+7 910 523 65 98",
type: "phone",
},
],
addresses: [
{
id: "1",
label: "Адрес регистрации",
value: "г.Рязань, ул.Мамина Сибиряка, д.54, кв 78",
type: "input",
},
],
};
},
};
</script>

View File

@@ -1,41 +0,0 @@
<template lang="pug">
.flex.flex-col.gap-y-2.wrapper.h-full.w-full
general-info(:edit-mode="editMode")
//- basic-data-form
//- contacts-form
//- documents-form
//- insurance-form
</template>
<script>
import GeneralInfo from "@/pages/newMedicalCard/tabs/general/GeneralInfo.vue";
import BasicDataForm from "@/pages/newMedicalCard/tabs/general/BasicDataForm.vue";
import DocumentsForm from "@/pages/newMedicalCard/tabs/general/DocumentsForm.vue";
import InsuranceForm from "@/pages/newMedicalCard/tabs/general/InsuranceForm.vue";
import ContactsForm from "@/pages/newMedicalCard/tabs/general/ContactsForm.vue";
export default {
name: "MedicalCardGeneralInfo",
props: {
editMode: Boolean,
},
components: {
BasicDataForm,
DocumentsForm,
InsuranceForm,
ContactsForm,
GeneralInfo,
},
};
</script>
<style lang="sass" scoped>
.wrapper
flex: 1
overflow-y: auto
min-width: 560px
min-height: 350px
@media (max-width: 600px)
width: fit-content
&::-webkit-scrollbar
width: 0
</style>

View File

@@ -1,9 +0,0 @@
export const contactInformation = {
options: ["Email", "Telegram", "Телефон"],
emptyMessage: "Отсутствует контактная информация",
};
export const addressInformation = {
options: ["Адрес регистрации", "Адрес проживания", "Адрес дополнительный"],
emptyMessage: "Отсутствует адрес",
};

View File

@@ -10,46 +10,49 @@ import pdfIcon from "@/assets/icons/pdf.svg";
import wordIcon from "@/assets/icons/word.svg"; import wordIcon from "@/assets/icons/word.svg";
import exelIcon from "@/assets/icons/exel.svg"; import exelIcon from "@/assets/icons/exel.svg";
export const specialMarksIconsConfig = {
hard: {
icon: "thumb_down_alt",
color: "amber-2",
bgColor: "#8d6e63",
description: "Конфликтный",
},
debt: {
icon: "paid",
color: "yellow",
bgColor: "green",
description: "Долг",
},
allergic: {
icon: "bolt",
color: "red",
bgColor: "orange",
description: "Аллергик",
},
infected: {
icon: "sick",
color: "light-green-9",
bgColor: "#afb42b",
description: "Опасное заболевание",
},
ban: {
icon: "dangerous",
color: "grey-6",
bgColor: "black",
description: "Не записывать",
},
unknown: {
icon: "help",
color: "grey-6",
bgColor: "#bdbdbd",
description: "Неопознанная метка",
},
};
export const baseDataForm = [ export const baseDataForm = [
{
dataLabel: "Личные данные",
dataKey: "personalData",
fields: [
{
key: "last_name",
label: "Фамилия",
type: "text",
disabled: true,
},
{
key: "first_name",
label: "Имя",
type: "text",
disabled: true,
},
{
key: "patronymic",
label: "Отчество",
type: "text",
disabled: true,
},
{
key: "gender",
label: "Пол",
type: "select",
disabled: true,
},
{
key: "birth_date",
label: "Дата рождения",
type: "date",
disabled: true,
},
{
key: "photo",
label: "Фото",
type: "avatar",
disabled: true,
},
],
},
{ {
dataLabel: "Адрес регистрации", dataLabel: "Адрес регистрации",
dataKey: "registrationAddress", dataKey: "registrationAddress",
@@ -465,6 +468,7 @@ export const documentForm = [
{ {
dataLabel: "Паспорт", dataLabel: "Паспорт",
dataKey: "passport", dataKey: "passport",
dataClass: "passport",
fields: [ fields: [
{ {
key: "series", key: "series",
@@ -529,6 +533,7 @@ export const documentForm = [
{ {
dataLabel: "СНИЛС", dataLabel: "СНИЛС",
dataKey: "insurance_number", dataKey: "insurance_number",
dataClass: "insurance",
fields: [ fields: [
{ {
key: "number", key: "number",
@@ -551,6 +556,7 @@ export const documentForm = [
{ {
dataLabel: "ИНН", dataLabel: "ИНН",
dataKey: "tax_identification_number", dataKey: "tax_identification_number",
dataClass: "tax",
fields: [ fields: [
{ {
key: "number", key: "number",

4
src/shared/errors.js Normal file
View File

@@ -0,0 +1,4 @@
export const errors = {
internal_error: "Внутренняя ошибка",
validation_error: "Ошибка валидации",
};

View File

@@ -1,7 +1,6 @@
import { createStore } from "vuex"; import { createStore } from "vuex";
import medical from "./modules/medicalCard"; import medical from "./modules/medicalCard";
import calendar from "./modules/calendar"; import calendar from "./modules/calendar";
import person from "./modules/person";
export default createStore({ export default createStore({
state: { state: {
@@ -14,7 +13,6 @@ export default createStore({
modules: { modules: {
medical, medical,
calendar, calendar,
person,
}, },
getters: { getters: {
getUrl(state) { getUrl(state) {

View File

@@ -12,7 +12,7 @@ const state = () => ({
networks: [], networks: [],
}, },
basicData: { basicData: {
patient: { personalData: {
last_name: null, last_name: null,
first_name: null, first_name: null,
patronymic: null, patronymic: null,
@@ -129,7 +129,7 @@ const getters = {
) || {}; ) || {};
let person = state.medicalCard.person; let person = state.medicalCard.person;
return { return {
patient: { personalData: {
last_name: person?.last_name || "", last_name: person?.last_name || "",
first_name: person?.first_name || "", first_name: person?.first_name || "",
patronymic: person?.patronymic || "", patronymic: person?.patronymic || "",

View File

@@ -1,74 +0,0 @@
import { fetchWrapper } from "@/shared/fetchWrapper";
const state = () => ({
firstName: "",
lastName: "",
patronymic: "",
birthDate: "",
gender: "",
marks: [],
addresses: [
{
category: "fact",
full_address: "г.Рязань, ул. Новикова-Прибоя, д.43/1, кв.2",
},
{
category: "registration",
full_address: "г.Санкт-Себастьянг, ул. Шоссе Энтузиастов, д.243, кв.4",
},
],
contacts: [
{ category: "email", value: "dol@yandex.ru" },
{ category: "phone", value: "+79586541252" },
],
});
const getters = {
PERSON(state) {
return {
firstName: state.firstName,
lastName: state.lastName,
patronymic: state.patronymic,
birthDate: state.birthDate,
gender: state.gender,
marks: ["hard", "rich", "allergic", "infected", "ban"],
};
},
ADDRESSES(state) {
return state.addresses;
},
CONTACTS(state) {
return state.contacts;
},
};
const actions = {
GET_PERSON({ commit }, id) {
fetchWrapper
.get(`persons/${id}`)
.then((res) => {
commit("SET_PERSON", res);
})
.catch((e) => alert(e));
},
UPDATE_PERSON(context, { obj, id }) {
fetchWrapper.patch(`persons/${id}`, obj);
},
};
const mutations = {
SET_PERSON(state, payload) {
state.firstName = payload.first_name;
state.lastName = payload.last_name;
state.patronymic = payload.patronymic;
state.birthDate = payload.birth_date;
state.gender = payload.gender;
},
};
export default {
state,
getters,
actions,
mutations,
};