5 Commits
master ... dd

Author SHA1 Message Date
dderbentsov
92b7bd6d01 global rework patient info 2023-11-02 01:19:05 +03:00
dderbentsov
35e8740f04 ref med card tabs 2023-10-20 01:27:15 +03:00
dderbentsov
fbd6312520 ref карточка пациента 2023-10-20 01:21:11 +03:00
dderbentsov
5b5e04ca12 add edit mode button 2023-10-20 00:51:47 +03:00
dderbentsov
e9929481b3 fix sidebar styles 2023-10-20 00:25:55 +03:00
32 changed files with 740 additions and 350 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.wrapper(class="w-1/4 xl:w-1/3 sm:w-1/2") .absolute.top-0.p-2.right-0.overflow-hidden.z-50(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,9 +41,6 @@ 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 = 3000) => { export const addNotification = (id, title, message, type, lifeTime = 0) => {
notifications[id] = { notifications[id] = {
title, title,
message, message,

View File

@@ -10,13 +10,64 @@
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",
components: { BaseButtonSidebar }, emits: ["save", "cancel", "update:editMode"],
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.pt-4.px-2.pb-7.rounded .sidebar.flex.flex-col.justify-between.py-4.rounded.items-center
.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

@@ -0,0 +1,55 @@
<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

@@ -0,0 +1,58 @@
<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,7 +86,6 @@ 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,25 +21,16 @@
@filter="filterFn", @filter="filterFn",
:popup-content-style="popupContentStyle" :popup-content-style="popupContentStyle"
@blur="$emit('blur')" @blur="$emit('blur')"
:multiple="multiple"
) )
template(#selected, v-if="!multiple") template(#selected)
slot(name="selected") slot(name="selected")
template(v-slot:option="{itemProps, opt}", v-if="!customOption") template(v-slot:option="scope", v-if="!customOption")
q-item(v-bind="itemProps", style="justify-content: center", v-if="value?.icon") q-item(v-bind="scope.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="opt.icon", size="24px") q-icon.icon(:name="scope.opt.icon", size="24px")
q-item.item.px-4.py-2.multiple( q-item.item.px-4.py-2(v-bind="scope.itemProps", style="justify-content: center", v-else)
: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 {{ opt.label }} q-item-label.text-dark.text-base.font-medium {{ scope.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",
@@ -88,7 +79,6 @@ 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: {
@@ -258,14 +248,8 @@ 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,28 +5,11 @@
.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,9 +53,14 @@ export default {
...mapActions({ ...mapActions({
getMedicalCardData: "getMedicalCardDataByPersonId", getMedicalCardData: "getMedicalCardDataByPersonId",
createMedicalCard: "createMedicalCard", createMedicalCard: "createMedicalCard",
getPerson: "GET_PERSON",
}), }),
openMedicalCard() { openMedicalCard() {
this.$router.push("medical-card/" + this.person["medical_card_id"]); this.getPerson(this.person.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,8 +47,6 @@ 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",
@@ -72,7 +70,6 @@ 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: {
@@ -109,10 +106,6 @@ 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", :error="!!errors?.['start'] || !!errors?.['end']") base-input.input.no-border(size="XS", mask="##:## - ##:##", v-model="times" )
.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,12 +86,7 @@ export default {
MedcardModal, MedcardModal,
}, },
mixins: [v_model], mixins: [v_model],
props: { props: { currentStatus: Object, statuses: Array, choiceStatus: Function },
currentStatus: Object,
statuses: Array,
choiceStatus: Function,
errors: Object,
},
data() { data() {
return { return {
noname, noname,

View File

@@ -1,10 +1,11 @@
<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 medical-card-patient-form(:edit-mode="editMode", :need-save="needSave")
medical-base-info.mt-2 medical-card-tabs(v-model="currentMenuItem")
medical-card-general-info(:edit-mode="editMode").mt-2
.h-full .h-full
the-right-menu the-right-menu(v-model:edit-mode="editMode")
</template> </template>
<script> <script>
@@ -16,6 +17,9 @@ 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",
@@ -27,11 +31,14 @@ 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

@@ -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

@@ -1,28 +0,0 @@
<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.pr-6.flex.flex-col.justify-between.rounded.font-medium.text-m .patient-info.pt-4.flex.flex-col.rounded.font-medium.text-m
.ml-6.flex.justify-between .ml-4.flex.justify-between
//- TODO по клику переход на предыдущую страницу //- TODO по клику переход на предыдущую страницу
q-breadcrumbs q-breadcrumbs
template(v-slot:separator) template(v-slot:separator)
@@ -11,108 +11,92 @@
) )
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
special-marks(:marks="patient?.marks", :edit-mode="editMode")
q-btn(
@click="toggleEditMode", .ml-6.flex.gap-x-4.col-grow.items-center
:style="{backgroundColor: 'var(--bg-light-grey)', color: 'blue'}",
padding="10px",
)
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}}
template(v-if="!editMode") .flex.gap-y-1.flex-col(v-if="!editMode")
.flex.gap-y-1.flex-col .flex.items-center
.flex.items-center span.text-xxl.font-bold.mr-3(
span.text-xxl.font-bold.mr-3( :style="{color: 'var(--font-dark-blue-color)', 'line-height': '135%'}",
:style="{color: 'var(--font-dark-blue-color)', 'line-height': '135%'}", ) {{ patientFullName }}
) {{patientName}}
span.text-smm.label-color Добавлен в систему: span.date-color {{ patient?.gender }}
span.date-color {{ patientData?.gender }} span.date-color {{ patient?.birthDate }}
span.date-color {{ patientData?.birth_date }}
span.text-smm.label-color Спец отметки:
template(v-else)
base-input(
v-model="fullname"
size="M",
label="Фамилия"
)
base-input(
v-model="fullname"
size="M",
label="Имя"
)
base-input(
v-model="fullname"
size="M",
label="Отчество"
)
.flex.justify-between.pt-4
.flex
.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}}
.flex-col(v-else)
.flex.gap-x-2
base-input(
v-model="patientEdit.lastName"
size="S",
label="Фамилия"
)
base-input(
v-model="patientEdit.firstName"
size="S",
label="Имя"
)
base-input(
v-model="patientEdit.patronymic"
size="S",
label="Отчество"
)
.flex.gap-x-2
base-input(
v-model="patientEdit.birthDate"
size="S",
label="Дата рождения"
)
base-input(
v-model="patientEdit.gender"
size="S",
label="Пол"
)
</template> </template>
<script> <script>
import BaseInput from "@/components/base/BaseInput.vue"; import BaseInput from "@/components/base/BaseInput.vue";
import { v_model } from "@/shared/mixins/v-model"; import { routesDictionary } from "@/pages/newMedicalCard/utils/medicalConfig.js";
import { column } from "@/pages/clients/utils/tableConfig.js"; import SpecialMarks from "@/pages/newMedicalCard/components/SpecialMarks.vue";
import * as moment from "moment/moment";
import {
headerMenuItem,
routesDictionary,
} from "@/pages/newMedicalCard/utils/medicalConfig.js";
export default { export default {
name: "MedicalCardPatientForm", name: "MedicalCardPatientForm",
mixins: [v_model], props: {
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() {
return this.$store.state.medical.medicalCard; patientFullName() {
}, 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
@@ -120,38 +104,13 @@ 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,
}));
},
}, },
methods: { watch: {
selectItem(item) { editMode: {
this.value = item?.component; immediate: true,
}, handler(val) {
ckeckName(field) { val && (this.patientEdit = { ...this.patient });
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;
}, },
}, },
}; };
@@ -160,47 +119,11 @@ 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

@@ -0,0 +1,42 @@
<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

@@ -0,0 +1,91 @@
<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

@@ -15,13 +15,7 @@
.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(
@@ -64,7 +58,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";
@@ -88,7 +82,7 @@ export default {
components: { components: {
MedicalFormWrapper, MedicalFormWrapper,
BaseInput, BaseInput,
BaseAvatar,
BaseSelect, BaseSelect,
BaseModal, BaseModal,
BaseUploadPhoto, BaseUploadPhoto,
@@ -127,11 +121,11 @@ export default {
updateBasicData() { updateBasicData() {
const photoFormData = new FormData(); const photoFormData = new FormData();
if ( if (
this.basic.personalData.photo.file && this.basic.patient.photo.file &&
this.basic.personalData.photo.photo !== this.basic.patient.photo.photo !==
this.initDataBasic.personalData.photo.photo this.initDataBasic.patient.photo.photo
) { ) {
photoFormData.append("photo", this.basic.personalData?.photo?.file[0]); photoFormData.append("photo", this.basic.patient?.photo?.file[0]);
console.log("photo_update"); console.log("photo_update");
} }
Object.keys(this.basic).map((key) => { Object.keys(this.basic).map((key) => {
@@ -156,7 +150,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.personalData.id, id: this.basic.patient.id,
address: updateData, address: updateData,
category: key, category: key,
}); });

View File

@@ -8,12 +8,8 @@
:open-edit="openEdit", :open-edit="openEdit",
:save="saveChange" :save="saveChange"
) )
q-form.form-wrap.gap-x-6.w-full(ref="documentForm", :no-error-focus="true") q-form.form-wrap.gap-6.w-full(ref="documentForm", :no-error-focus="true")
.data-section.flex.flex-col.gap-2( .data-section.flex.flex-col.gap-2(v-for="data in configData", :key="data.dataLabel")
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",
@@ -70,9 +66,7 @@
: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"
) )
q-icon.my-auto.cursor-pointer.copy( .icon-copy.my-auto.text-lg.label-field.cursor-pointer(
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])"
) )
@@ -320,11 +314,10 @@ export default {
.form-wrap .form-wrap
display: grid display: grid
grid-template-columns: repeat(3, 1fr) grid-template-columns: repeat(3, 1fr)
row-gap: 32px grid-template-rows: repeat(1, 1fr)
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: 1.2fr 1.8fr grid-template-rows: 3fr 1fr
.label-field .label-field
min-width: 110px min-width: 110px
color: var(--font-grey-color) color: var(--font-grey-color)
@@ -343,15 +336,4 @@ 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

@@ -0,0 +1,79 @@
<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

@@ -0,0 +1,63 @@
<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

@@ -18,8 +18,9 @@
: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( .flex.gap-3.items-center.h-10.w-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(
@@ -89,8 +90,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.cursor-pointer.copy( q-icon.my-auto.text-lg.label-field.cursor-pointer.copy(
size="20px", size="18px",
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

@@ -0,0 +1,41 @@
<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

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

View File

@@ -10,49 +10,46 @@ 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 baseDataForm = [ export const specialMarksIconsConfig = {
{ hard: {
dataLabel: "Личные данные", icon: "thumb_down_alt",
dataKey: "personalData", color: "amber-2",
fields: [ bgColor: "#8d6e63",
{ description: "Конфликтный",
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,
},
],
}, },
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 = [
{ {
dataLabel: "Адрес регистрации", dataLabel: "Адрес регистрации",
dataKey: "registrationAddress", dataKey: "registrationAddress",
@@ -468,7 +465,6 @@ export const documentForm = [
{ {
dataLabel: "Паспорт", dataLabel: "Паспорт",
dataKey: "passport", dataKey: "passport",
dataClass: "passport",
fields: [ fields: [
{ {
key: "series", key: "series",
@@ -533,7 +529,6 @@ export const documentForm = [
{ {
dataLabel: "СНИЛС", dataLabel: "СНИЛС",
dataKey: "insurance_number", dataKey: "insurance_number",
dataClass: "insurance",
fields: [ fields: [
{ {
key: "number", key: "number",
@@ -556,7 +551,6 @@ export const documentForm = [
{ {
dataLabel: "ИНН", dataLabel: "ИНН",
dataKey: "tax_identification_number", dataKey: "tax_identification_number",
dataClass: "tax",
fields: [ fields: [
{ {
key: "number", key: "number",

View File

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

View File

@@ -1,6 +1,7 @@
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: {
@@ -13,6 +14,7 @@ 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: {
personalData: { patient: {
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 {
personalData: { patient: {
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

@@ -0,0 +1,74 @@
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,
};