51 Commits

Author SHA1 Message Date
dderbentsov
516ec12114 first step 2023-10-14 19:32:12 +03:00
dderbentsov
74a4ded35c поиск карт 2023-10-09 00:24:56 +03:00
Kirill Andrusyak
35cc0ee059 Merge branch 'fix-basic-data-forms' into 'master'
Изменила сетки форм

See merge request astra/astra-frontend!511
2023-10-03 17:40:39 +00:00
Daria Golova
019c8c0459 Изменила сетки форм 2023-09-26 18:05:18 +03:00
kandrusyak
615200f4ca medcards list fixes 2023-09-25 21:41:28 +03:00
kandrusyak
7e9632436e base data fixes 2023-09-25 21:30:24 +03:00
kandrusyak
dd9124b1c5 small screen fix 2023-09-25 21:15:32 +03:00
kandrusyak
535c7562c7 small screen fix 2023-09-25 21:12:05 +03:00
kandrusyak
f86c8ad5ba fixes 2023-09-25 20:32:55 +03:00
Kirill Andrusyak
d130bdf40c Merge branch 'add-admin-calendar' into 'master'
Добавлен календарь администратора

See merge request astra/astra-frontend!509
2023-09-21 20:19:22 +00:00
Daria Golova
7b2e07f0d2 Поправила баг в документах 2023-09-19 17:05:29 +03:00
Daria Golova
48e2571c77 WIP Стянула записи приемов с бэка 2023-09-19 13:31:05 +03:00
Daria Golova
fc6c5e61fb WIP Разделила календари 2023-09-18 18:53:45 +03:00
dderbentsov
c2eb6bf1e9 создал homepage 2023-09-17 15:44:30 +03:00
dderbentsov
4bdd665d1f убрал список клиентов с бокового меню 2023-09-17 14:20:51 +03:00
dderbentsov
297f11952b удалил старые pages 2023-09-17 14:10:31 +03:00
dderbentsov
dd5e9eed54 Убрал поиск с хедера 2023-09-17 14:05:18 +03:00
andrusyakka
c986e312fe disable e2e pipeline 2023-09-11 18:53:10 +03:00
Kirill Andrusyak
14424c541c Merge branch 'create_client' into 'master'
Create client

See merge request astra/astra-frontend!508
2023-09-11 15:41:38 +00:00
andrusyakka
71c6ba7a74 add create medical card 2023-09-11 18:40:15 +03:00
kandrusyak
1ee0e8f0e8 add create patient popup 2023-09-11 18:22:55 +03:00
kandrusyak
85b68c726b update pipeline 2023-09-05 02:19:15 +03:00
kandrusyak
11d7f0af42 update pipeline 2023-09-05 02:11:04 +03:00
kandrusyak
b7c45e80c3 fix Dockerfile 2023-09-05 02:05:48 +03:00
kandrusyak
4915cf2506 add e2e 2023-09-05 01:35:25 +03:00
kandrusyak
dc65af6c83 fix create form and fix origin 2023-09-04 21:02:44 +03:00
kandrusyak
66efe5bc95 fix create form and fix origin 2023-09-04 20:57:34 +03:00
Kirill Andrusyak
e7b9cce5a2 Merge branch '8-editing-documents-block' into 'master'
Редактирование блока документы и страховка на мед.карте, исправить отображение плашек в календаре для пациента с большим количеством контактов

See merge request astra/astra-frontend!507
2023-08-29 18:03:22 +00:00
Daria Golova
15e31ebcd7 Исправила отображение контактов на записи 2023-08-25 14:28:47 +03:00
Daria Golova
d1c5dc5683 WIP Сделала валидацию на страховках 2023-08-25 14:28:47 +03:00
Daria Golova
cb41a00ae1 WIP Изменила отправку документов 2023-08-25 14:28:47 +03:00
Daria Golova
4987a3ae09 WIP Изменила инпут, сделала документы 2023-08-25 14:28:47 +03:00
Kirill Andrusyak
817f881159 fix namesapce 2023-08-24 20:41:21 +00:00
Kirill Andrusyak
332fff778a Merge branch '19-design-bugs' into 'master'
Фикс багов дизайна

Closes #19

See merge request astra/astra-frontend!506
2023-08-23 19:35:36 +00:00
megavrilinvv
564b250b66 Фикс багов дизайна 2023-08-23 16:34:44 +03:00
Kirill Andrusyak
e9810c91cf Merge branch '15-fixed-contacts' into 'master'
Контакты: фикс запросов, фикс списка соцсетей

Closes #15

See merge request astra/astra-frontend!505
2023-08-22 19:21:01 +00:00
megavrilinvv
1f47f9abe6 Контакты: фикс запросов, фикс списка соцсетей 2023-08-18 18:21:18 +03:00
Kirill Andrusyak
bc5c694657 Merge branch '10-editing-blocks' into 'master'
Resolve "При нажатии не происходит перехода на мед.карту", "Редактирование основных данных на мед.карте"

Closes #10

See merge request astra/astra-frontend!503
2023-08-17 17:45:43 +00:00
megavrilinvv
6337aa80dd Merge branch '10-editing-blocks' of https://gitlab.dopcore.com/andrusyakka/urban-couscous into 10-editing-blocks 2023-08-17 18:19:35 +03:00
megavrilinvv
fbb89646ea [WIP] Фикс запросов, добавил валидацию основных данных 2023-08-17 18:17:16 +03:00
kandrusyak
65918134ec change disabled method 2023-08-17 16:33:16 +03:00
megavrilinvv
e3ad1396b6 [WIP] Фикс запросов на основыне данные, добавил запросы для контактов 2023-08-17 16:32:44 +03:00
megavrilinvv
15cb788fcc [WIP] Исправил отправку основных данных 2023-08-17 16:32:44 +03:00
megavrilinvv
b7af4b15ca [WIP] Добавил переход на медкарту, возможность редактирования основных данных 2023-08-17 16:32:44 +03:00
Kirill Andrusyak
37f5520796 Merge branch '9-reschedule-calendar-interval' into 'master'
Перенесла интервал календаря в параметры и починила запрос

See merge request astra/astra-frontend!504
2023-08-17 10:15:34 +00:00
Daria Golova
715fc9824e Поправила кнопки на сайдбаре 2023-08-16 23:53:12 +03:00
Daria Golova
8691790a53 Добавила интервал дат в параметры 2023-08-16 23:30:25 +03:00
Daria Golova
f459620040 Merge branch '4-fix-date-display' into 'master'
Поправила отображение даты (4), undefined в хедере мед.карты (13), создание осмотра (12), поменяла индикацию заполненности медкарты (11),

See merge request astra/astra-frontend!502
2023-08-16 20:28:51 +00:00
Daria Golova
dcbbf20916 Поправила url картинок 2023-08-16 23:26:10 +03:00
Daria Golova
7f655dcdc9 Исправила undefined в хедере медкарты 2023-08-16 14:26:13 +03:00
Daria Golova
c866662384 Исправила отображения дат, статусов, врача 2023-08-16 14:04:21 +03:00
107 changed files with 11656 additions and 6152 deletions

View File

@@ -2,3 +2,16 @@ include:
- project: 'astra/microservice-ci' - project: 'astra/microservice-ci'
ref: main ref: main
file: '/templates/microservice.yaml' file: '/templates/microservice.yaml'
#e2e-dev:
# image: node:18.10.0
# stage: deploy
# dependencies:
# - deploy-dev
# only:
# - master
# services:
# - selenium/standalone-chrome
# script:
# - npm ci
# - npm run test:e2e --host=selenium__standalone-chrome --baseUrl=https://astra-dev.dopcore.com

View File

@@ -1,4 +1,4 @@
FROM node:16-alpine AS builder FROM docker.io/node:16-alpine AS builder
WORKDIR /front WORKDIR /front
@@ -7,7 +7,7 @@ COPY . .
RUN npm ci RUN npm ci
RUN npm run build RUN npm run build
FROM nginx:1.21.6-alpine as runner FROM docker.io/nginx:1.21.6-alpine as runner
COPY ./deploy/nginx.conf /etc/nginx/nginx.conf COPY ./deploy/nginx.conf /etc/nginx/nginx.conf
COPY --from=builder /front/dist /var/www/html COPY --from=builder /front/dist /var/www/html

View File

@@ -2,7 +2,6 @@ apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: {{ .Values.app.fullName }} name: {{ .Values.app.fullName }}
namespace: default
spec: spec:
ports: ports:
- port: {{ .Values.app.server.port }} - port: {{ .Values.app.server.port }}

8842
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,8 @@
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
"build": "vue-cli-service build", "build": "vue-cli-service build",
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint",
"test:e2e": "wdio run ./wdio.conf.js"
}, },
"dependencies": { "dependencies": {
"@dop/astra-ui": "^0.2.5", "@dop/astra-ui": "^0.2.5",
@@ -24,6 +25,10 @@
"@vue/cli-plugin-eslint": "~5.0.0", "@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-vuex": "^5.0.8", "@vue/cli-plugin-vuex": "^5.0.8",
"@vue/cli-service": "~5.0.0", "@vue/cli-service": "~5.0.0",
"@wdio/cli": "^8.16.3",
"@wdio/jasmine-framework": "^8.16.3",
"@wdio/local-runner": "^8.16.3",
"@wdio/spec-reporter": "^8.16.3",
"@webdiscus/pug-loader": "^2.9.4", "@webdiscus/pug-loader": "^2.9.4",
"eslint": "^8.25.0", "eslint": "^8.25.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -25,7 +25,7 @@
import TheHeader from "@/components/TheHeader"; import TheHeader from "@/components/TheHeader";
import TheSidebar from "@/components/TheSidebar"; import TheSidebar from "@/components/TheSidebar";
import TheNotificationProvider from "@/components/Notifications/TheNotificationProvider"; import TheNotificationProvider from "@/components/Notifications/TheNotificationProvider";
import { mapState } from "vuex";
export default { export default {
name: "LoggedInLayout", name: "LoggedInLayout",
components: { TheNotificationProvider, TheHeader, TheSidebar }, components: { TheNotificationProvider, TheHeader, TheSidebar },
@@ -33,10 +33,14 @@ export default {
return { return {
isOpenForm: false, isOpenForm: false,
updatedClients: false, updatedClients: false,
url: "https://astra-dev.dopcore.com/api/store/",
createdClientId: "", createdClientId: "",
}; };
}, },
computed: {
...mapState({
url: (state) => state.imgUrl,
}),
},
methods: { methods: {
openForm() { openForm() {
this.isOpenForm = true; this.isOpenForm = true;

View File

@@ -0,0 +1,183 @@
<template lang="pug">
.flex.flex-col.pt-6.gap-y-4(:style="{maxWidth: '682px'}")
base-input-full-name(v-model="patientData" )
.flex.flex-col.flex-auto.l.gap-y-8
.flex
button.title-info.px-6.py-2.cursor-pointer.w-full.text-sm(
v-for="form in forms",
@click="selectForm(form.component)",
:class="{active: form.component === currentForm}",
:key="form.id",
:id="form.id",
:disabled="form.disabled"
) {{form.title}}
component(
v-bind:is="currentForm",
v-model="patientData"
)
.footer.flex.gap-2
base-button(type="secondary", label="Отменить", width="126px", @click="$emit('close')")
base-button(width="168px", label="Создать", @click="createPatient")
</template>
<script>
import { v_model } from "@/shared/mixins/v-model";
import FormCreateBasicInfo from "@/pages/clients/components/FormCreateBasicInfo.vue";
import FormCreateIdentityDocuments from "@/pages/clients/components/FormCreateIdentityDocuments.vue";
import FormCreateAddresses from "@/pages/clients/components/FormCreateAddresses.vue";
import FormCreateAttachments from "@/pages/clients/components/FormCreateAttachments.vue";
import BaseInputFullName from "@/components/base/BaseInputFullName.vue";
import HeaderRecordForm from "../pages/newCalendar/components/HeaderRecordForm.vue";
import BaseCalendar from "@/components/base/Calendar/BaseCalendar.vue";
import BaseButton from "@/components/base/BaseButton.vue";
import BaseModal from "@/components/base/BaseModal.vue";
import ServicesModal from "../pages/newCalendar/components/ServicesModal.vue";
import { fetchWrapper } from "@/shared/fetchWrapper";
const forms = [
{
title: "Основное",
id: "basic",
component: "form-create-basic-info",
},
{
title: "ДУЛ",
id: "doc",
component: "form-create-identity-documents",
disabled: true,
},
{
title: "Адрес",
id: "address",
component: "form-create-addresses",
disabled: true,
},
{
title: "Дополнительное",
id: "additional",
component: "form-create-attachments",
disabled: true,
},
];
export default {
name: "PatientCreationForm",
components: {
FormCreateBasicInfo,
FormCreateIdentityDocuments,
FormCreateAddresses,
FormCreateAttachments,
BaseInputFullName,
BaseCalendar,
HeaderRecordForm,
BaseButton,
BaseModal,
ServicesModal,
},
emits: ["close"],
mixins: [v_model],
data() {
return {
patientData: {
full_name: "",
phone: "",
birth_date: "",
},
currentForm: "form-create-basic-info",
forms,
};
},
computed: {
nameObject() {
const nameArr = this.patientData?.full_name?.split(" ");
return {
last_name: nameArr?.[0] || "",
first_name: nameArr?.[1] || "",
patronymic: nameArr?.[2] || "",
};
},
},
methods: {
async createPatient() {
const body = {
...this.nameObject,
};
if (this.patientData.phone) {
body.contacts = [
{
category: "PHONE",
value: this.patientData.phone,
},
];
}
if (this.patientData.birth_date)
body.birth_date = this.patientData.birth_date;
const data = await fetchWrapper.post("persons/", body);
this.$emit("close", data);
},
handleChangePatientData(val) {
this.patientData = { ...this.patientData, ...val };
},
selectForm(componentName) {
this.currentForm = this.forms.find(
(elem) => elem.component === componentName
)?.component;
},
},
};
</script>
<style lang="sass" scoped>
.dark-blue
color: var(--font-dark-blue-color)
min-width: 50px
.text
color: var(--font-grey-color)
width: 76px
.change
background: var(--bg-light-grey)
color: var(--font-grey-color)
.services
background: var(--bg-light-grey)
color: var(--font-grey-color)
width: 560px
.service
display: -webkit-box
overflow: hidden
.gradient
width: 10%
height: 28px
right: 0
.other
width: 46px
background: var(--bg-light-grey)
.price
background: var(--bg-light-grey)
width: 82px
color: var(--font-grey-color)
.title-info
color: var(--font-grey-color)
border-bottom: 1.5px solid var(--font-grey-color)
&:hover
color: var(--btn-blue-color)
border-bottom: 1.5px solid var(--btn-blue-color)
&.active
color: var(--btn-blue-color)
border-bottom: 1.5px solid var(--btn-blue-color)
.footer
border-top: 1px solid var(--border-light-grey-color)
margin: 40px -32px 0px
padding: 16px 32px 0px 32px
.icon :deep(path)
fill: var(--font-grey-color)
</style>

View File

@@ -6,7 +6,6 @@
alt="Logo", alt="Logo",
@click="redirectHomePage" @click="redirectHomePage"
) )
header-inputs
.flex.ml-auto.items-center.gap-x-4 .flex.ml-auto.items-center.gap-x-4
q-btn( q-btn(
@@ -89,7 +88,7 @@ export default {
}, },
methods: { methods: {
redirectHomePage() { redirectHomePage() {
this.$router.push("/calendar"); this.$router.push("/");
}, },
async logout() { async logout() {
await fetchWrapper.post("auth/logout"); await fetchWrapper.post("auth/logout");

View File

@@ -0,0 +1,34 @@
<template lang="pug">
.sidebar.h-full.rounded.flex.flex-col.justify-between.pt-4.px-2
.flex.flex-col.gap-y-4
q-btn(
style="width: 48px; height: 48px",
id="print",
rounded,
padding="0px",
icon="print",
text-color="blue",
size="xl"
)
</template>
<script>
import BaseButtonSidebar from "@/components/base/BaseSidebarButton";
export default {
name: "TheRightMenu",
components: { BaseButtonSidebar },
};
</script>
<style lang="sass" scoped>
.sidebar
max-width: 64px
min-width: 64px
background-color: var(--default-white)
.button:hover
background-color: var(--bg-light-blue-color)
color: var(--btn-blue-color)
.hover :deep(path)
fill: #9294A7
</style>

View File

@@ -34,12 +34,6 @@ export default {
active: true, active: true,
icon: "calendar", icon: "calendar",
}, },
{
id: "user",
path: "#/clients",
active: false,
icon: "clients",
},
{ {
id: "medcards", id: "medcards",
path: "#/medcards", path: "#/medcards",
@@ -70,17 +64,9 @@ export default {
return this.pageSettings.find((el) => el.id === "settings"); return this.pageSettings.find((el) => el.id === "settings");
}, },
}, },
mounted() {
let href = window.location.href.slice(22);
this.pageSettings.forEach((el, index) => {
el.path === href.substr(href.lastIndexOf("#"))
? (this.pageSettings[index].active = true)
: (this.pageSettings[index].active = false);
});
},
watch: { watch: {
"$route.path"() { "$route.path"(value) {
if (this.$router.currentRoute._value.fullPath === "/calendar") { if (value === "/calendar") {
this.currentPageBorder = true; this.currentPageBorder = true;
this.pageSettings.forEach((el) => this.pageSettings.forEach((el) =>
el.path === "#/calendar" ? (el.active = true) : (el.active = false) el.path === "#/calendar" ? (el.active = true) : (el.active = false)
@@ -88,6 +74,13 @@ export default {
} else this.currentPageBorder = false; } else this.currentPageBorder = false;
}, },
}, },
mounted() {
this.pageSettings.forEach((el, index) => {
el.path === "#" + this.$route.path
? (this.pageSettings[index].active = true)
: (this.pageSettings[index].active = false);
});
},
}; };
</script> </script>

View File

@@ -15,7 +15,7 @@
iconLeft iconLeft
) )
template(v-slot:iconLeft) template(v-slot:iconLeft)
q-icon(name="app:icon-search", size="20px", style="color: var(--font-grey-color)") q-icon.icon(name="app:search", size="20px", style="color: var(--font-grey-color)")
.field.flex.flex-col.overflow-y-scroll(:class="{'filed-category': findSelected(selected?.id)}") .field.flex.flex-col.overflow-y-scroll(:class="{'filed-category': findSelected(selected?.id)}")
.flex.items-center(v-for="benefit in benefitData", :key="benefit.id") .flex.items-center(v-for="benefit in benefitData", :key="benefit.id")
q-item(tag="label", :style="{alignItems: benefit.name.length > 28 ? 'start' : 'center'}") q-item(tag="label", :style="{alignItems: benefit.name.length > 28 ? 'start' : 'center'}")
@@ -43,7 +43,7 @@
@click="saveCategories(selected)" @click="saveCategories(selected)"
) )
.right-side.flex.px-10.font-bold.relative .right-side.flex.px-10.font-bold.relative
q-icon(name="app:icon-cancel").text-sm.absolute.top-4.right-4.cursor-pointer(@click="closeModalCategories") q-icon(name="app:cancel").text-sm.absolute.top-4.right-4.cursor-pointer(@click="closeModalCategories")
.flex.gap-y-6.flex-col.w-full(v-if="selected?.id && selected?.name !== 'Без льготы'") .flex.gap-y-6.flex-col.w-full(v-if="selected?.id && selected?.name !== 'Без льготы'")
.right-header.flex .right-header.flex
span.text-6xl {{selected?.name}} span.text-6xl {{selected?.name}}
@@ -185,6 +185,9 @@ export default {
.grey-color .grey-color
color: var(--font-grey-color) color: var(--font-grey-color)
.icon :deep(path)
fill: var(--font-grey-color)
.q-item .q-item
padding: 0 padding: 0
min-height: 40px min-height: 40px

View File

@@ -1,5 +1,5 @@
<template lang="pug"> <template lang="pug">
base-input-container.gap-y-2(:important="important", :label="label", :style="{width: width + 'px', ...sizeVariable}") base-input-container.gap-y-2(:important="important", :label="label", :style="{width: widthInternal, ...sizeVariable}")
q-input.input( q-input.input(
v-model="value", v-model="value",
:name="name", :name="name",
@@ -24,7 +24,7 @@
: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)"
) )
template(v-slot:prepend, v-if="iconLeft") template(v-slot:prepend, v-if="iconLeft")
@@ -55,6 +55,7 @@ export default {
default: false, default: false,
}, },
type: { type: {
type: String,
default: "text", default: "text",
}, },
accept: { accept: {
@@ -68,7 +69,7 @@ export default {
shadowText: String, shadowText: String,
mask: String, mask: String,
debounce: [String, Number], debounce: [String, Number],
width: Number, width: [String, Number],
rule: Array, rule: Array,
lazyRule: [Boolean, String], lazyRule: [Boolean, String],
itemAligned: Boolean, itemAligned: Boolean,
@@ -152,6 +153,10 @@ export default {
"--py": "8px 0", "--py": "8px 0",
}; };
}, },
widthInternal() {
if (typeof this.width === "number") return this.width + "px";
return this.width;
},
}, },
}; };
</script> </script>
@@ -232,4 +237,10 @@ export default {
.input :deep(.q-field__marginal) .input :deep(.q-field__marginal)
height: auto !important height: auto !important
.input :deep(.q-field__shadow)
opacity: 0.5
top: 3px
color: var(--font-grey-color)
margin-left: 8px
</style> </style>

View File

@@ -1,5 +1,5 @@
<template lang="pug"> <template lang="pug">
base-input-container.gap-y-2(:label="label", :style="{width: width + 'px', ...sizeVariable }") base-input-container.gap-y-2(:label="label", :style="{width: widthInternal, ...sizeVariable }")
q-input.input( q-input.input(
v-model="value", v-model="value",
:name="name", :name="name",
@@ -9,7 +9,7 @@
:readonly="readonly", :readonly="readonly",
:disable="disabled", :disable="disabled",
mask="##.##.####", mask="##.##.####",
:rules="[checkInput]", :rules="[(val) => rule ? rule(val) : defaultRule(val)]",
:lazy-rules="lazyRule", :lazy-rules="lazyRule",
:item-aligned="itemAligned", :item-aligned="itemAligned",
no-error-icon, no-error-icon,
@@ -57,10 +57,11 @@ export default {
default: "none", default: "none",
}, },
debounce: [String, Number], debounce: [String, Number],
width: Number, width: [String, Number],
lazyRule: [Boolean, String], lazyRule: [Boolean, String],
itemAligned: Boolean, itemAligned: Boolean,
modelValue: Date, rule: Function,
modelValue: String,
placeholder: String, placeholder: String,
disabled: Boolean, disabled: Boolean,
label: String, label: String,
@@ -79,27 +80,15 @@ export default {
computed: { computed: {
value: { value: {
get() { get() {
if ( this.changeInternalDate(
moment(this.modelValue).isValid() && moment(this.checkFormat(this.modelValue)).isValid()
typeof this.modelValue === "object" ? moment(this.modelValue)
) {
this.changeInternalDate(moment(this.modelValue));
return moment(this.modelValue).format("DD.MM.YYYY");
}
if (moment(this.convertFormat(this.modelValue)).isValid()) {
this.changeInternalDate(moment(this.modelValue));
return this.modelValue;
}
this.changeInternalDate(null);
return null;
},
set(value) {
this.$emit(
"update:modelValue",
moment(this.convertFormat(value)).isValid()
? new Date(moment(this.convertFormat(value)))
: null : null
); );
return this.convertISOFormat(this.modelValue);
},
set(value) {
this.$emit("update:modelValue", this.convertRUFormat(value));
}, },
}, },
sizeVariable() { sizeVariable() {
@@ -146,15 +135,29 @@ export default {
}; };
} }
}, },
widthInternal() {
if (typeof this.width === "number") return this.width + "px";
return this.width;
},
}, },
methods: { methods: {
convertFormat(date) { convertISOFormat(date) {
return date && date?.length === 10 return date?.split("-")?.reverse()?.join(".");
? date.split(".").reverse().join("-")
: null;
}, },
checkInput(val) { convertRUFormat(date) {
return moment(this.convertFormat(val)).isValid(); return date?.split(".")?.reverse()?.join("-");
},
checkFormat(date) {
return date && date?.length === 10 ? date : null;
},
defaultRule(val) {
return (
!val ||
moment(
this.checkFormat(val) ? this.convertRUFormat(val) : null
).isValid()
);
}, },
closeCalendar() { closeCalendar() {
this.calendarVisibility = false; this.calendarVisibility = false;

View File

@@ -41,7 +41,7 @@
padding="2px 0 0 0" padding="2px 0 0 0"
) )
q-icon(name="app:ok", size="20px") q-icon(name="app:ok", size="20px")
base-input.w-full(v-model="infoClient.basic.full_name", placeholder="ФИО пациента*", size="M") base-input.w-full(v-model="value.full_name", placeholder="ФИО пациента*", size="M")
</template> </template>
<script> <script>
@@ -49,11 +49,12 @@ import BaseModal from "./BaseModal.vue";
import addImageIcon from "@/assets/icons/photo.svg"; import addImageIcon from "@/assets/icons/photo.svg";
import BaseButton from "@/components/base/BaseButton.vue"; import BaseButton from "@/components/base/BaseButton.vue";
import BaseInput from "@/components/base/BaseInput.vue"; import BaseInput from "@/components/base/BaseInput.vue";
import { v_model } from "@/shared/mixins/v-model";
export default { export default {
name: "BaseInputFullName", name: "BaseInputFullName",
components: { BaseModal, BaseInput, BaseButton }, components: { BaseModal, BaseInput, BaseButton },
props: { infoClient: Object }, mixins: [v_model],
data() { data() {
return { return {
defaultIcon: addImageIcon, defaultIcon: addImageIcon,

View File

@@ -15,9 +15,8 @@
:popup-content-style="{height: candidates?.length > 3 ? '216px' : ''}" :popup-content-style="{height: candidates?.length > 3 ? '216px' : ''}"
v-model="value" v-model="value"
ref="selectRef" ref="selectRef"
@blur="pasteFullName"
) )
//template(#selected)
// .text-dark.text-sm.font-medium(v-if="person") {{ person.last_name + ' ' + person.first_name + ' ' + person.patronymic }}
template(v-slot:iconLeft) template(v-slot:iconLeft)
q-icon.search-icon(name="app:search", size="20px") q-icon.search-icon(name="app:search", size="20px")
template(v-slot:customOption="{props, data}") template(v-slot:customOption="{props, data}")
@@ -48,7 +47,7 @@ export default {
BaseSelect, BaseSelect,
}, },
mixins: [v_model], mixins: [v_model],
emits: ["createPerson"], emits: ["create-person"],
data() { data() {
return { return {
fullName: "", fullName: "",
@@ -60,6 +59,7 @@ export default {
}, },
computed: { computed: {
pickedFullName() { pickedFullName() {
if (!this.value.first_name) return "";
return ( return (
this.value.last_name + this.value.last_name +
" " + " " +
@@ -70,13 +70,14 @@ export default {
}, },
}, },
watch: { watch: {
pickedFullName(val) { pickedFullName() {
this.$refs?.selectRef?.updateInputValue(val, true); this.pasteFullName();
}, },
}, },
methods: { methods: {
createPerson() { createPerson() {
this.$emit("createPerson"); this.$refs?.selectRef?.hidePopup();
this.$emit("create-person");
}, },
filterFn(val, update) { filterFn(val, update) {
fetchWrapper.get(`persons/?full_name=${val}`).then((result) => { fetchWrapper.get(`persons/?full_name=${val}`).then((result) => {
@@ -87,8 +88,10 @@ export default {
}); });
}); });
}, },
test(slotProps) { pasteFullName() {
console.log(slotProps); this.$nextTick(() => {
this.$refs?.selectRef?.updateInputValue(this.pickedFullName, true);
});
}, },
}, },
}; };

View File

@@ -1,6 +1,6 @@
<template lang="pug"> <template lang="pug">
.flex.flex-col.gap-y-2 .flex.flex-col.gap-y-2
base-input-container(:label="label", :style="{width: width, ...sizeVariable}") base-input-container(:label="label", :style="{width: widthInternal, ...sizeVariable}")
q-select.select( q-select.select(
ref="selectRef" ref="selectRef"
v-model="value", v-model="value",
@@ -20,8 +20,9 @@
:placeholder="useInput && placeholder ? placeholder : ''", :placeholder="useInput && placeholder ? placeholder : ''",
@filter="filterFn", @filter="filterFn",
:popup-content-style="popupContentStyle" :popup-content-style="popupContentStyle"
@blur="$emit('blur')"
) )
template(v-slot:selected) template(#selected)
slot(name="selected") slot(name="selected")
template(v-slot:option="scope", v-if="!customOption") template(v-slot:option="scope", v-if="!customOption")
q-item(v-bind="scope.itemProps", style="justify-content: center", v-if="value?.icon") q-item(v-bind="scope.itemProps", style="justify-content: center", v-if="value?.icon")
@@ -79,7 +80,7 @@ export default {
filterFn: Function, filterFn: Function,
popupContentStyle: Object, popupContentStyle: Object,
}, },
emits: ["update:modelValue"], emits: ["update:modelValue", "blur"],
computed: { computed: {
value: { value: {
get() { get() {
@@ -158,11 +159,18 @@ export default {
: !!this.value?.label, : !!this.value?.label,
}; };
}, },
widthInternal() {
if (typeof this.width === "number") return this.width + "px";
return this.width;
},
}, },
methods: { methods: {
updateInputValue(value, noFilter) { updateInputValue(value, noFilter) {
this.$refs?.selectRef?.updateInputValue(value, noFilter); this.$refs?.selectRef?.updateInputValue(value, noFilter);
}, },
hidePopup() {
this.$refs?.selectRef?.hidePopup();
},
}, },
}; };
</script> </script>

View File

@@ -21,7 +21,7 @@
round, round,
padding="2px 0 0 0" padding="2px 0 0 0"
) )
q-icon(name="app:ok", size="20px") q-icon.icon(name="app:ok", size="40px")
</template> </template>
<script> <script>
@@ -118,4 +118,7 @@ export default {
min-height: 400px min-height: 400px
height: 100% height: 100%
border-radius: 50% border-radius: 50%
.icon :deep(path)
fill: var(--default-white)
</style> </style>

View File

@@ -1,8 +1,8 @@
<template lang="pug"> <template lang="pug">
.wrapper-addresses.h-full .wrapper-form.h-full
.flex.flex-col.gap-y-6 .flex.flex-col.gap-y-6
base-input( base-input(
v-model="addresses.full_address", v-model="form.full_address",
placeholder="Введите адрес целиком", placeholder="Введите адрес целиком",
label="Полный адрес", label="Полный адрес",
size="M" size="M"
@@ -12,42 +12,42 @@
span.text-sm.separator.px-2 или span.text-sm.separator.px-2 или
.grid.grid-cols-2.gap-y-6.gap-x-4 .grid.grid-cols-2.gap-y-6.gap-x-4
base-input( base-input(
v-model="addresses.city", v-model="form.city",
label="Город", label="Город",
placeholder="Введите город", placeholder="Введите город",
disabled, disabled,
size="M" size="M"
) )
base-input( base-input(
v-model="addresses.region", v-model="form.region",
label="Область", label="Область",
placeholder="Введите область", placeholder="Введите область",
disabled, disabled,
size="M" size="M"
) )
base-input( base-input(
v-model="addresses.street", v-model="form.street",
label="Улица", label="Улица",
placeholder="Введите улицу", placeholder="Введите улицу",
disabled, disabled,
size="M" size="M"
) )
base-input( base-input(
v-model="addresses.house", v-model="form.house",
label="Дом", label="Дом",
placeholder="Номер дома", placeholder="Номер дома",
disabled, disabled,
size="M" size="M"
) )
base-input( base-input(
v-model="addresses.flat", v-model="form.flat",
label="Квартира", label="Квартира",
placeholder="Номер квартиры", placeholder="Номер квартиры",
disabled, disabled,
size="M" size="M"
) )
base-input( base-input(
v-model="addresses.zip_code", v-model="form.zip_code",
label="Индекс", label="Индекс",
mask="######", mask="######",
placeholder="000000", placeholder="000000",
@@ -63,17 +63,32 @@ import BaseInput from "@/components/base/BaseInput.vue";
export default { export default {
name: "FormCreateAddresses", name: "FormCreateAddresses",
components: { BaseInput, BaseCustomSelect }, components: { BaseInput, BaseCustomSelect },
props: { emits: ["change"],
addresses: Object, data() {
saveClient: Function, return {
saveFile: Function, form: {
full_address: "",
city: "",
region: "",
street: "",
house: "",
flat: "",
zip_code: "",
},
};
},
watch: {
form(val) {
this.$emit("change", val);
},
}, },
}; };
</script> </script>
<style lang="sass" scoped> <style lang="sass" scoped>
.wrapper-addresses .wrapper-form
height: 380px height: 380px
width: 570px
overflow-y: auto overflow-y: auto
&::-webkit-scrollbar &::-webkit-scrollbar
width: 4px width: 4px

View File

@@ -2,7 +2,7 @@
.wrapper.flex.flex-col.flex-auto.h-full.gap-y-8.justify-between .wrapper.flex.flex-col.flex-auto.h-full.gap-y-8.justify-between
.place.flex.flex-col.justify-center.items-center.py-3 .place.flex.flex-col.justify-center.items-center.py-3
.upload.text-center.text-sm.flex.w-fit .upload.text-center.text-sm.flex.w-fit
input.hidden(@change="(e) => addAttachment(e)" type="file" id="file-upload") input.hidden(@change="() => {}" type="file" id="file-upload")
span Загрузите элемент span Загрузите элемент
label.label.cursor-pointer(for="file-upload") с компьютера label.label.cursor-pointer(for="file-upload") с компьютера
span или перетащите их сюда span или перетащите их сюда
@@ -11,9 +11,6 @@
<script> <script>
export default { export default {
name: "FormCreateAttachments", name: "FormCreateAttachments",
props: {
addAttachment: Function,
},
}; };
</script> </script>

View File

@@ -1,46 +1,21 @@
<template lang="pug"> <template lang="pug">
.wrapper-info.h-full .wrapper-info
.grid.grid-cols-2.gap-x-4.gap-y-6 .grid.grid-cols-2.gap-x-4.gap-y-6
base-select(
:items="priorityList",
placeholder="Приоритет клиента",
v-model="basicInfo.priority",
label="Приоритет",
size="M"
)
base-input-date( base-input-date(
v-model="basicInfo.birth_date", v-model="value.birth_date",
size="M", size="M",
important, important,
label="Дата рождения", label="Дата рождения",
placeholder="Дата рождения" placeholder="Дата рождения"
) )
base-input( base-input(
v-model="phone.username", v-model="value.phone",
placeholder="+7 (915) 6449223", placeholder="+7 (915) 6449223",
mask="+7 (###) ###-##-##", mask="+7 (###) ###-##-##",
label="Номер телефона", label="Номер телефона",
size="M", size="M",
important important
) )
base-input(
v-model="email.username",
placeholder="user@yandex.ru",
label="Email",
important,
size="M",
)
.flex.flex-col.col-start-1.col-end-3.w-100(class="gap-y-1.5")
span.text-sm.font-semibold.opacity-40.input-info Ссылки на соцсети
.flex(class="gap-x-1.5" v-for="network in basicInfo.contacts" :key="network.kind.id")
base-adding-network(
:items="networksList",
:network="network",
)
span.add-network.cursor-pointer(
v-show="networksList.length !== 0",
@click="addNetwork"
) Добавить соцсеть
</template> </template>
<script> <script>
@@ -48,6 +23,7 @@ import BaseAddingNetwork from "@/components/base/BaseAddingNetwork";
import BaseSelect from "@/components/base/BaseSelect"; import BaseSelect from "@/components/base/BaseSelect";
import BaseInput from "@/components/base/BaseInput.vue"; import BaseInput from "@/components/base/BaseInput.vue";
import BaseInputDate from "@/components/base/BaseInputDate.vue"; import BaseInputDate from "@/components/base/BaseInputDate.vue";
import { v_model } from "@/shared/mixins/v-model";
export default { export default {
name: "FormCreateBasicInfo", name: "FormCreateBasicInfo",
@@ -57,22 +33,12 @@ export default {
BaseSelect, BaseSelect,
BaseInputDate, BaseInputDate,
}, },
props: { mixins: [v_model],
priorityList: Array,
phone: Object,
email: Object,
basicInfo: Object,
saveClient: Function,
addNetwork: Function,
networksList: Array,
},
}; };
</script> </script>
<style lang="sass" scoped> <style lang="sass" scoped>
.wrapper-info .wrapper-info
max-height: 336px
min-height: 272px
overflow-y: auto overflow-y: auto
&::-webkit-scrollbar &::-webkit-scrollbar
width: 4px width: 4px

View File

@@ -4,27 +4,27 @@
span.title-info.text-base.font-bold Паспортные данные span.title-info.text-base.font-bold Паспортные данные
.grid.grid-cols-2.gap-x-4.gap-y-6 .grid.grid-cols-2.gap-x-4.gap-y-6
base-input( base-input(
v-model="identityDocument.pass.series_number", v-model="form.series_number",
mask="#### ######", mask="#### ######",
placeholder="0000 000000", placeholder="0000 000000",
label="Серия и номер", label="Серия и номер",
size="M" size="M"
) )
base-input( base-input(
v-model="identityDocument.pass.issued_by_org", v-model="form.issued_by_org",
placeholder="Точно как в паспорте", placeholder="Точно как в паспорте",
label="Кем выдан", label="Кем выдан",
size="M" size="M"
) )
base-input( base-input(
v-model="identityDocument.pass.issued_by_org_code", v-model="form.issued_by_org_code",
mask="###-###", mask="###-###",
placeholder="000000", placeholder="000000",
label="Код подразделения", label="Код подразделения",
size="M" size="M"
) )
base-input-date( base-input-date(
v-model="identityDocument.pass.issued_by_date", v-model="form.issued_by_date",
placeholder="Дата", placeholder="Дата",
label="Дата выдачи", label="Дата выдачи",
size="M" size="M"
@@ -33,7 +33,7 @@
span.title-info.text-base.font-bold СНИЛС и ИНН span.title-info.text-base.font-bold СНИЛС и ИНН
.grid.grid-cols-2.gap-x-4.gap-y-6 .grid.grid-cols-2.gap-x-4.gap-y-6
base-input( base-input(
v-model="identityDocument.snils.number", v-model="form.snils",
mask="###-###-### ##", mask="###-###-### ##",
placeholder="000000000 00", placeholder="000000000 00",
label="Номер СНИЛС", label="Номер СНИЛС",
@@ -41,7 +41,7 @@
size="M" size="M"
) )
base-input( base-input(
v-model="identityDocument.inn.number", v-model="form.inn",
placeholder="000000000000", placeholder="000000000000",
mask="############", mask="############",
label="Номер ИНН", label="Номер ИНН",
@@ -57,9 +57,23 @@ import BaseInputDate from "@/components/base/BaseInputDate.vue";
export default { export default {
name: "FormCreateIdentityDocuments", name: "FormCreateIdentityDocuments",
components: { BaseInput, BaseInputDate }, components: { BaseInput, BaseInputDate },
props: { emits: ["change"],
identityDocument: Object, data() {
saveClient: Function, return {
form: {
series_number: "",
issued_by_org: "",
issued_by_org_code: "",
issued_by_date: "",
snils: "",
inn: "",
},
};
},
watch: {
form(val) {
this.$emit("change", val);
},
}, },
}; };
</script> </script>

View File

@@ -0,0 +1,29 @@
<template lang="pug">
.wrapper.flex.flex-col.w-full.bg-white.p-4
.text-xxl.font-bold.pa-3
.text Добрый день, %username%!
.text.pt-3.cursor-pointer(@click="gotoCalendar") Расписание приемов
.text.pt-3.cursor-pointer(@click="gotoMedicalCards") Медицинские карты
</template>
<script>
export default {
name: "TheHome",
methods: {
gotoCalendar() {
this.$router.push("/calendar");
},
gotoMedicalCards() {
this.$router.push("/medcards");
},
},
};
</script>
<style lang="sass" scoped>
.wrapper
overflow: auto
border-top-left-radius: 4px
border-top-right-radius: 4px
</style>

View File

@@ -21,7 +21,7 @@ export default {
medicalCards: [], medicalCards: [],
dataStatus: { dataStatus: {
title: "no data", title: "no data",
message: "Введите номер карты или ФИО пациента", message: "Введите данные Пациента для поиска",
img: nothingChooseImg, img: nothingChooseImg,
}, },
}; };
@@ -31,20 +31,21 @@ export default {
if (!value) { if (!value) {
this.dataStatus = { this.dataStatus = {
title: "no data", title: "no data",
message: "Введите номер карты или ФИО пациента", message: "Введите ФИО Пациента",
img: nothingChooseImg, img: nothingChooseImg,
}; };
this.medicalCards = []; this.medicalCards = [];
return; return;
} }
const data = await fetchWrapper.get(
"medical_cards?searchstring=" + value const data = await fetchWrapper.post("persons/search", {
); full_name: value,
});
if (data?.length === 0) { if (data?.length === 0) {
this.medicalCard = []; this.medicalCard = [];
this.dataStatus = { this.dataStatus = {
title: "not found", title: "not found",
message: `По запросу «${value}» не найдено. message: `По запросу «${value}» Пациентов не найдено.
Переформулируйте запрос и попробуйте снова`, Переформулируйте запрос и попробуйте снова`,
img: nothingSearchImg, img: nothingSearchImg,
}; };

View File

@@ -4,15 +4,17 @@
base-input.search( base-input.search(
:width="438", :width="438",
size="M", size="M",
placeholder="Введите ФИО или номер телефона", placeholder="ФИО Пациента",
icon-left, icon-left,
v-model="filterString" v-model="filterString"
) )
template(#iconLeft) template(#iconLeft)
q-icon.search-icon(name="app:search", size="20px") q-icon.search-icon(name="app:search", size="20px")
base-button(width="216px", @click="openCreateMedcardPage") base-button(@click="showCreateModal = true")
q-icon.plus.mr-2(name="app:plus", size="24px") q-icon.plus.mr-2(name="app:plus", size="18px")
span Создать медкарту span Новая медкарта
base-modal(v-model="showCreateModal", title="Создать медицинскую карту", modal-padding)
patient-creation-form(@close="handleClosePatientForm")
</template> </template>
<script> <script>
@@ -21,19 +23,37 @@ import BaseSelect from "@/components/base/BaseSelect.vue";
import BaseButton from "@/components/base/BaseButton.vue"; import BaseButton from "@/components/base/BaseButton.vue";
import debounce from "lodash/debounce"; import debounce from "lodash/debounce";
import BaseModal from "@/components/base/BaseModal.vue";
import PatientCreationForm from "@/components/PatientCreationForm.vue";
import { mapActions } from "vuex";
export default { export default {
name: "MedicalCardSearchHeader", name: "MedicalCardSearchHeader",
components: { BaseInput, BaseSelect, BaseButton }, components: {
PatientCreationForm,
BaseModal,
BaseInput,
BaseSelect,
BaseButton,
},
emits: ["search"], emits: ["search"],
data() { data() {
return { return {
filterString: "", filterString: "",
showCreateModal: false,
}; };
}, },
methods: { methods: {
openCreateMedcardPage() { handleClosePatientForm(val) {
alert("Создать медкарту с ФИО: " + this.filterString); this.showCreateModal = false;
if (!val) return;
this.getMedicalCardData({
personId: val.id,
successCallback: (id) => this.$router.push("medical-card/" + id),
});
}, },
...mapActions({
getMedicalCardData: "getMedicalCardDataByPersonId",
}),
}, },
watch: { watch: {
filterString: debounce(function (value) { filterString: debounce(function (value) {
@@ -52,7 +72,8 @@ export default {
fill: var(--font-grey-color) fill: var(--font-grey-color)
.search :deep(.q-field__prepend) .search :deep(.q-field__prepend)
padding-right: 4px padding-right: 12px
.plus :deep(path) .plus :deep(path)
fill: var(--default-white) fill: var(--default-white)

View File

@@ -17,7 +17,7 @@
v-for="medcard in medicalCards", v-for="medcard in medicalCards",
:key="medcard.id" :key="medcard.id"
:header-style="headerStyle", :header-style="headerStyle",
:medcard-info="medcard", :person="medcard",
) )
</template> </template>
@@ -40,8 +40,7 @@ export default {
methods: { methods: {
headerStyle(field) { headerStyle(field) {
return { return {
width: field.width, ...field,
"justify-content": field.title === "Do" ? "center" : "",
}; };
}, },
}, },

View File

@@ -1,99 +1,68 @@
<template lang="pug"> <template lang="pug">
.w-full.row-wrapper.h-14.flex .w-full.row-wrapper.h-14.flex
.field.gap-x-3( .field(:style="headerStyle(headerConfig[0])")
:style="headerStyle(headerConfig[0])" template(v-if="person.medical_card_id")
) base-button(size="S", @click="openMedicalCard", label="Открыть", color="green")
q-avatar(size="36px") template(v-else)
img(:src="avatar") base-button(size="S", @click="createMedCard", label="Создать")
span.font-semibold.text-dark {{fullName}} .field.gap-x-3(:style="headerStyle(headerConfig[1])")
.field(:style="headerStyle(headerConfig[1])") span.font-semibold.text-dark {{patientName}}
span.text-dark {{ formatDate(person?.birth_date) }} г. .field(:style="headerStyle(headerConfig[2])")
.field.gap-x-2(:style="headerStyle(headerConfig[2])") span.text-dark {{ birthDate }}
.rounded-full.h-2.w-2(:style="{background: patientPriority?.color}") .field(:style="{...headerStyle(headerConfig[3])}")
span(:style="{color: patientPriority?.color}") {{ patientPriority?.text }} span.text-dark {{ phones }}
.field(:style="headerStyle(headerConfig[3])")
.medcard-number.rounded.h-7.py-1.pl-3.pr-2.flex.items-center.justify-between.w-full.cursor-pointer(
@click="copyMedicalCardNumber"
)
span.text-dark.cursor-pointer {{ medcardInfo?.number }}
q-icon.copy(size="18px", name="app:copy")
.field(:style="headerStyle(headerConfig[4])")
span.text-dark {{ formatDate(medcardInfo?.created_at) }} г.
.field.gap-x-3(:style="headerStyle(headerConfig[5])")
.track.h-2.flex-1.rounded
.thumb.h-full.rounded(:style="fillingThumbStyle")
span.grey-color {{ medcardInfo?.filling_percentage || 0 }}%
.field(:style="{...headerStyle(headerConfig[6])}")
q-icon.medcard.cursor-pointer(name="app:medcard", size="20px", @click="openMedicalCard")
</template> </template>
<script> <script>
import { import { searchListConfig } from "@/pages/medcards/utils/medcardsConfig.js";
searchListConfig,
priorityList,
} from "@/pages/medcards/utils/medcardsConfig.js";
import * as moment from "moment/moment"; import * as moment from "moment/moment";
import TheNotificationProvider from "@/components/Notifications/TheNotificationProvider"; import TheNotificationProvider from "@/components/Notifications/TheNotificationProvider";
import { addNotification } from "@/components/Notifications/notificationContext";
import avatar from "@/assets/images/person.png";
import { mapActions } from "vuex"; import { mapActions } from "vuex";
import BaseButton from "@/components/base/BaseButton.vue";
export default { export default {
name: "MedicalCardSearchRow", name: "MedicalCardSearchRow",
components: { TheNotificationProvider }, components: { TheNotificationProvider, BaseButton },
props: { props: {
headerStyle: Function, headerStyle: Function,
medcardInfo: Object, person: Object,
}, },
data() { data() {
return { return {
headerConfig: searchListConfig, headerConfig: searchListConfig,
avatar,
}; };
}, },
computed: { computed: {
fullName() { patientName() {
return `${this.person?.last_name} ${this.person?.first_name} ${this.person?.patronymic}`; return `${this.person?.last_name} ${this.person?.first_name} ${this.person?.patronymic}`;
}, },
person() { birthDate() {
return this.medcardInfo?.person; let retVal =
this.person?.birth_date &&
moment(this.person?.birth_date)?.format("DD MMMM YYYY");
return retVal ? retVal + " г." : "";
}, },
patientPriority() { phones() {
return priorityList?.find( return this.person?.contacts
({ priority }) => (this.medcardInfo.priority || null) === priority ?.filter((el) => el.category === "PHONE")
); .map((el) => el.value)
}, .join(", ");
fillingThumbStyle() {
let percentage = this.medcardInfo?.filling_percentage || 0;
return {
width: `calc(${percentage}%)`,
"background-color":
percentage < 11
? "var(--system-color-red)"
: percentage < 51
? "var(--bg-yellow-warning)"
: "var(--system-color-green)",
};
}, },
}, },
methods: { methods: {
...mapActions({ ...mapActions({
getMedicalCardData: "getMedicalCardDataByPersonId", getMedicalCardData: "getMedicalCardDataByPersonId",
createMedicalCard: "createMedicalCard",
}), }),
openMedicalCard() { openMedicalCard() {
this.$router.push("medical-card/" + this.medcardInfo.id); this.$router.push("medical-card/" + this.person["medical_card_id"]);
}, },
formatDate(date) { createMedCard() {
return moment(date)?.format("DD MMMM YYYY"); this.createMedicalCard({
}, personId: this.person.id,
copyMedicalCardNumber() { }).then(() => {
navigator.clipboard.writeText(this.medcardInfo.number); this.openMedicalCard();
addNotification( });
new Date().getTime(),
"",
"Номер медкарты скопирован",
"success",
5000
);
}, },
}, },
}; };
@@ -129,8 +98,6 @@ span
.track .track
background: var(--gray-thirdly) background: var(--gray-thirdly)
.thumb
.grey-color .grey-color
color: var(--font-grey-color) color: var(--font-grey-color)
</style> </style>

View File

@@ -1,57 +1,19 @@
export const searchListConfig = [ export const searchListConfig = [
{
title: "Медкарта",
minWidth: "140px",
},
{ {
title: "ФИО", title: "ФИО",
width: "550px", width: "100%",
minWidth: "350px",
}, },
{ {
title: "Дата рождения", title: "Дата рождения",
width: "220px", minWidth: "220px",
}, },
{ {
title: "Приоритет", title: "Телефон",
width: "220px", minWidth: "220px",
},
{
title: "№ медкарты",
width: "280px",
},
{
title: "Дата создания",
width: "215px",
},
{
title: "Процент заполнения",
width: "295px",
},
{
title: "Do",
width: "60px",
},
];
export const priorityList = [
{
priority: 1,
id: 1,
text: "Высокий",
color: "var(--system-color-red)",
},
{
priority: 2,
id: 2,
text: "Средний",
color: "var(--bg-yellow-warning)",
},
{
priority: 3,
id: 3,
text: "Низкий",
color: "var(--btn-blue-color)",
},
{
priority: null,
id: 4,
text: "-",
color: "#9294A7",
}, },
]; ];

View File

@@ -2,8 +2,11 @@
.w-full.flex .w-full.flex
calendar-sidebar(v-if="!isOpen", :open-sidebar="openSidebar", :create-form="createForm") calendar-sidebar(v-if="!isOpen", :open-sidebar="openSidebar", :create-form="createForm")
calendar-open-sidebar(v-else, :open-sidebar="openSidebar", :create-form="createForm") calendar-open-sidebar(v-else, :open-sidebar="openSidebar", :create-form="createForm")
calendar-wrapper.ml-2(:open-sidebar="isOpen") component.ml-2(
base-modal(v-model="isShowForm", title="Создание записи <Переделка>", modal-padding) v-bind:is="calendarComponent",
:open-sidebar="isOpen",
)
base-modal(v-model="isShowForm", title="Создание записи", modal-padding)
create-event-form(:close-form="closeForm") create-event-form(:close-form="closeForm")
base-modal(v-model="previewVisibility", :hideHeader="true", :modalPadding="true") base-modal(v-model="previewVisibility", :hideHeader="true", :modalPadding="true")
calendar-record-preview(v-model:preview-visibility="previewVisibility") calendar-record-preview(v-model:preview-visibility="previewVisibility")
@@ -12,7 +15,8 @@
<script> <script>
import CalendarSidebar from "@/pages/newCalendar/components/CalendarSidebar"; import CalendarSidebar from "@/pages/newCalendar/components/CalendarSidebar";
import CalendarOpenSidebar from "@/pages/newCalendar/components/CalendarOpenSidebar"; import CalendarOpenSidebar from "@/pages/newCalendar/components/CalendarOpenSidebar";
import CalendarWrapper from "@/pages/newCalendar/components/CalendarWrapper"; import DoctorCalendarWrapper from "@/pages/newCalendar/components/doctorCalendar/CalendarWrapper";
import ManagerCalendarWrapper from "@/pages/newCalendar/components/managerCalendar/CalendarWrapper";
import CreateEventForm from "@/pages/newCalendar/components/CreateEventForm"; import CreateEventForm from "@/pages/newCalendar/components/CreateEventForm";
import * as moment from "moment/moment"; import * as moment from "moment/moment";
import BaseModal from "@/components/base/BaseModal.vue"; import BaseModal from "@/components/base/BaseModal.vue";
@@ -23,7 +27,8 @@ export default {
components: { components: {
CalendarSidebar, CalendarSidebar,
CalendarOpenSidebar, CalendarOpenSidebar,
CalendarWrapper, DoctorCalendarWrapper,
ManagerCalendarWrapper,
CreateEventForm, CreateEventForm,
BaseModal, BaseModal,
CalendarRecordPreview, CalendarRecordPreview,
@@ -39,7 +44,14 @@ export default {
computed: { computed: {
...mapState({ ...mapState({
selectedRecordId: (state) => state.calendar.selectedRecordId, selectedRecordId: (state) => state.calendar.selectedRecordId,
selectedDates: (state) => state.calendar.selectedDates,
calendarShape: (state) => state.calendarShape,
}), }),
calendarComponent() {
return this.calendarShape === "doctor"
? "doctor-calendar-wrapper"
: "manager-calendar-wrapper";
},
}, },
methods: { methods: {
openSidebar() { openSidebar() {
@@ -53,7 +65,12 @@ export default {
}, },
...mapActions({ ...mapActions({
changeSelectedRecordId: "changeSelectedRecordId", changeSelectedRecordId: "changeSelectedRecordId",
changeSelectedDates: "changeSelectedDates",
getEvents: "getEvents",
}), }),
convertDate(date) {
return moment(date?.split(".")?.reverse()?.join("-"));
},
}, },
watch: { watch: {
selectedRecordId(value) { selectedRecordId(value) {
@@ -62,6 +79,61 @@ export default {
previewVisibility(value) { previewVisibility(value) {
if (!value) this.changeSelectedRecordId(null); if (!value) this.changeSelectedRecordId(null);
}, },
"$route.query": {
immediate: true,
deep: true,
handler(value) {
if (this.$route.path === "/calendar") {
let dateType = this.calendarShape === "doctor" ? "week" : "day";
if (!value?.start || !value?.end) {
this.changeSelectedDates({
from: moment().clone().startOf(dateType),
to: moment().clone().endOf(dateType),
});
this.$router.replace({
query: {
start: this.selectedDates?.from?.format("DD.MM.YYYY"),
end: this.selectedDates?.to?.format("DD.MM.YYYY"),
},
});
} else if (
value?.start !== this.selectedDates?.from?.format("DD.MM.YYYY") ||
value?.end !== this.selectedDates?.to?.format("DD.MM.YYYY")
) {
this.changeSelectedDates({
from: this.convertDate(value?.start).clone().startOf("day"),
to: this.convertDate(value?.end).clone().endOf("day"),
});
}
}
},
},
selectedDates: {
deep: true,
immediate: true,
handler(newVal, oldVal) {
if (
!newVal?.from?.isSame(oldVal?.from) ||
!newVal?.to?.isSame(oldVal?.to)
) {
this.getEvents();
if (
this.$route.query?.start !== newVal?.from?.format("DD.MM.YYYY") &&
this.$route.query?.end !== newVal?.to?.format("DD.MM.YYYY")
) {
this.$router.replace({
query: {
start: newVal?.from?.format("DD.MM.YYYY"),
end: newVal?.to?.format("DD.MM.YYYY"),
},
});
}
}
},
},
},
beforeUnmount() {
this.$route.query = {};
}, },
}; };
</script> </script>

View File

@@ -15,7 +15,7 @@ export default {
timeCoil: Array, timeCoil: Array,
currentTime: String, currentTime: String,
dayEndTime: Number, dayEndTime: Number,
isCurrentWeek: Boolean, isCurrent: Boolean,
}, },
computed: { computed: {
currentHour() { currentHour() {
@@ -33,7 +33,7 @@ export default {
if ( if (
convertTime(elem, 0, 3) === this.currentHour && convertTime(elem, 0, 3) === this.currentHour &&
!this.isEndDay && !this.isEndDay &&
this.isCurrentWeek this.isCurrent
) { ) {
return { return {
"current-time": true, "current-time": true,

View File

@@ -1,112 +0,0 @@
<template lang="pug">
.calendar-column-wrapper.flex.flex-col
.header.flex.flex-col.items-center.justify-center.top-0.pt-3.pb-10px
span.font-bold.text-base.color-black {{ date?.format("D MMMM")}}
span.text-smm.color-grey {{ transformDayName(date?.format("dddd"))}}
.body.h-full.px-1.py-1
calendar-record-card(
v-for="record in filteredRecords",
:key="record?.id",
:record="record",
:expanded-type="expandedDisplayType",
:style="eventCardPosition(record.start, record.end)",
@click="changeSelectedRecordId(record?.id)",
)
.footer.flex.items-center.justify-center.bg-white.h-9(v-if="!expandedDisplayType")
span.text-smm.color-grey 0 записей
</template>
<script>
import * as moment from "moment/moment";
import CalendarRecordCard from "@/pages/newCalendar/components/CalendarRecordCard.vue";
import { verifyTime } from "@/pages/newCalendar/utils/calendarFunctions.js";
import { mapActions, mapState } from "vuex";
export default {
name: "CalendarColumn",
components: { CalendarRecordCard },
props: {
date: Object,
expandedDisplayType: Boolean,
dayEndTime: Number,
dayStartTime: Number,
},
data() {
return {
pixelsPerHour: 76,
};
},
computed: {
...mapState({
events: (state) => state.calendar.events || [],
}),
filteredRecords() {
return this.events.filter(
(elem) =>
moment.parseZone(elem.start).format("YYYY-MM-DD") ===
this.date.format("YYYY-MM-DD")
);
},
pixelsPerMinute() {
return this.pixelsPerHour / 60;
},
},
methods: {
...mapActions({
changeSelectedRecordId: "changeSelectedRecordId",
}),
transformDayName(name) {
return name[0]?.toUpperCase() + name?.slice(1);
},
eventCardPosition(startTime, endTime) {
let start = startTime
.slice(11, -4)
.split(":")
.map((elem) => parseInt(elem, 10));
let end = endTime.slice(11, -4);
let position =
(start[0] - this.dayStartTime) * this.pixelsPerHour +
start[1] * this.pixelsPerMinute +
4;
if (
parseInt(start[0], 10) < this.dayStartTime ||
verifyTime(end) > this.dayEndTime
) {
return {
top: "0px",
visibility: "hidden",
};
}
return {
top: `${position}px`,
};
},
},
};
</script>
<style lang="sass" scoped>
.calendar-column-wrapper
border-right: 1px solid var(--border-light-grey-color)
&:last-child
border-right: none
.header
position: sticky
z-index: 5
background-color: var(--default-white)
.body
position: relative
z-index: 3
.color-black
color: var(--font-dark-blue-color)
.color-grey
color: var(--font-grey-color)
.footer
border-top: 1px solid var(--border-light-grey-color)
@media(min-height: 1090px)
border-bottom: 1px solid var(--border-light-grey-color)
</style>

View File

@@ -15,14 +15,17 @@
size="20px", size="20px",
@click.stop="changeTimeDisplay(true)" @click.stop="changeTimeDisplay(true)"
) )
.color-black.pl-4.flex-1.font-semibold.py-6px.relative( .color-black.pl-4.flex-1.font-semibold.py-6px.relative.cursor-default(
:class="{'pl-6px collapsed-name': expandedTime}" :class="{'pl-6px collapsed-name': expandedTime}"
) {{trimPatientName(record?.person?.last_name, record?.person?.first_name, record?.person?.patronymic)}} ) {{trimPatientName(record?.person?.last_name, record?.person?.first_name, record?.person?.patronymic)}}
.gradient.absolute.w-5.h-full.right-0(v-if="expandedTime") .gradient.absolute.w-5.h-full.right-0(v-if="expandedTime")
.info-block.justify-center .info-block.justify-center(v-if="expandedType")
img.w-5.h-5(:src="recordingStatus?.icon") q-icon(
.last-item.info-block.justify-center(v-if="expandedType") :name="record?.person?.has_medical_card ? 'app:medcard-green' : 'app:medcard'",
img.w-5.h-5(:src="medicalСardStatus?.icon") size="20px",
)
.last-item.info-block.justify-center
.recording-status(:class="recordingStatus?.class")
.body.background-grey.flex.flex-col.justify-between.pl-4.pr-2.color-grey( .body.background-grey.flex.flex-col.justify-between.pl-4.pr-2.color-grey(
:style="bodyHeight", :style="bodyHeight",
:class="bodyPadding" :class="bodyPadding"
@@ -30,12 +33,12 @@
.flex.gap-x-14(:class="bodyClass") .flex.gap-x-14(:class="bodyClass")
.flex.gap-x-2.items-center( .flex.gap-x-2.items-center(
v-for="contact in contacts", v-for="contact in contacts",
:key="contact.value", :key="contact?.value",
) )
q-icon.network-icon(:name="contact.icon", size="16px") q-icon.network-icon(:name="contact?.icon", size="16px")
.text-xxs {{ contact.value }} .text-xxs.cursor-default {{ contact?.value }}
.h-6.w-6.color-black.background-dark-grey.text-xxs.rounded.flex.items-center.justify-center( .h-6.w-6.color-black.background-dark-grey.text-xxs.rounded.flex.items-center.justify-center.cursor-default(
v-if="collapsedDisplayCondition" v-if="collapsedDisplayCondition && contact"
) +1 ) +1
</template> </template>
@@ -61,15 +64,27 @@ export default {
}, },
computed: { computed: {
contacts() { contacts() {
let contactList = this.record?.person?.contacts let contactList = [];
?.filter( this.record?.person?.contacts?.find((elem) => {
(elem) => elem.category === "PHONE" || elem.category === "EMAIL" if (elem.category === "PHONE") {
) contactList.push({
?.map((elem) => ({ kind: elem?.category,
kind: elem.category, icon: networks?.find((item) => item.id === elem.category)?.icon,
icon: networks.find((item) => item.id === elem.category)?.icon, value: elem?.value,
value: elem.value, });
})); return true;
}
});
this.record?.person?.contacts?.find((elem) => {
if (elem.category === "EMAIL") {
contactList.push({
kind: elem?.category,
icon: networks?.find((item) => item.id === elem.category)?.icon,
value: elem?.value,
});
return true;
}
});
if (this.collapsedDisplayCondition) { if (this.collapsedDisplayCondition) {
contactList.length = 1; contactList.length = 1;
return contactList; return contactList;
@@ -80,14 +95,10 @@ export default {
return !this.bodyClass?.["flex-col"] && !this.expandedType; return !this.bodyClass?.["flex-col"] && !this.expandedType;
}, },
recordingStatus() { recordingStatus() {
return statuses[0].data.find( const status = statuses?.[0]?.data?.find(
(elem) => elem.value === this.record?.status (elem) => elem.value === this.record?.status
); );
}, return status || statuses?.[0]?.data?.[0];
medicalСardStatus() {
return statuses[1].data.find(
(elem) => elem.value === this.record?.medicalCard?.status
);
}, },
startTime() { startTime() {
return moment.parseZone(this.record?.start); return moment.parseZone(this.record?.start);
@@ -205,4 +216,18 @@ export default {
fill: var(--font-dark-blue-color) fill: var(--font-dark-blue-color)
.time-icon :deep(path) .time-icon :deep(path)
fill: var(--font-grey-color) fill: var(--font-grey-color)
.recording-status
border-radius: 9999px
width: 17px
height: 17px
.not-accepted
border: 1.2px solid var(--font-grey-color)
.accepted
background-color: var(--system-color-green)
.rejected
background-color: var(--system-color-red)
.reception
background-color: var(--btn-blue-color)
span
cursor: default
</style> </style>

View File

@@ -1,295 +0,0 @@
<template lang="pug">
.schedule.flex-1.flex.flex-col.relative(
:style="{width: openSidebar ? 'calc(100vw - 320px)' : 'calc(100vw - 160px)'}"
)
calendar-header.w-full.mb-1(v-model="displayType")
.schedule-body.h-full.bg-white.w-full(:class="{rounded: !expandedDisplayType}")
.column-wrapper.flex.ml-20(:style="columnWrapperWidth")
calendar-column(
v-for="date in dateRange",
:key="date",
:date="date",
:style="columnHeight",
:expanded-display-type="expandedDisplayType",
:day-start-time="validateStartTime"
:day-end-time="validateEndTime"
)
.flex.relative(
:style="expandedDisplayType ? backgroundWrapperWidth : {}",
:class="{'border-bottom': expandedDisplayType}"
)
.time-coil-wrapper.left-0.-mt-12
calendar-clock-column(
:time-coil="timeCoil",
:current-time="currentTime",
:is-current-week="isCurrentWeek",
:day-end-time="validateEndTime"
)
.time-circle-indicator.left-74px.h-3.w-3.rounded-full.absolute(
v-if="isShownIndicator",
:style="circleIndicatorLocation"
)
span.time-line-indicator.block.left-20.absolute(
v-if="isShownIndicator",
:style="lineIndicatorLocation"
)
calendar-background.flex-1
.w-full.h-3.bg-white.footer(v-if="expandedDisplayType")
</template>
<script>
import CalendarHeader from "@/pages/newCalendar/components/CalendarHeader";
import CalendarBackground from "@/pages/newCalendar/components/CalendarBackground.vue";
import CalendarClockColumn from "@/pages/newCalendar/components/CalendarClockColumn.vue";
import CalendarColumn from "@/pages/newCalendar/components/CalendarColumn.vue";
import {
convertTime,
verifyTime,
} from "@/pages/newCalendar/utils/calendarFunctions.js";
import { mapState, mapActions } from "vuex";
import * as moment from "moment/moment";
export default {
name: "CalendarWrapper",
components: {
CalendarHeader,
CalendarBackground,
CalendarClockColumn,
CalendarColumn,
},
props: {
openSidebar: Boolean,
},
data() {
return {
displayType: "expanded",
currentTime: "",
isCurrentWeek: true,
isShownIndicator: true,
timeCoil: [],
timer: null,
pixelsPerHour: 76,
};
},
computed: {
...mapState({
workingHours: (state) => state.calendar.workingHours,
selectedDates: (state) => state.calendar.selectedDates,
}),
hours() {
return convertTime(this.currentTime, 0, -6);
},
minutes() {
return convertTime(this.currentTime, 3, -3);
},
hoursMinutes() {
return this.currentTime.slice(0, -3);
},
pixelsPerMinute() {
return this.pixelsPerHour / 60;
},
backgroundHeight() {
return (
(this.validateEndTime - this.validateStartTime) * this.pixelsPerHour
);
},
expandedDisplayType() {
return this.displayType === "expanded";
},
dateRange() {
let diff = this.selectedDates?.to.diff(this.selectedDates?.from, "days");
let range = [];
for (let i = 0; i <= diff; i++) {
range.push(this.selectedDates?.from.clone().add(i, "day"));
}
return range;
},
validateStartTime() {
return verifyTime(this.workingHours.start);
},
validateEndTime() {
return verifyTime(this.workingHours.end);
},
columnHeight() {
return this.expandedDisplayType
? {
height: `${this.timeCoil.length * this.pixelsPerHour + 60}px`,
width: "380px",
}
: {
height: `${this.timeCoil.length * this.pixelsPerHour + 96}px`,
width: this.openSidebar
? `calc((100vw - 320px - 80px) / ${this.dateRange.length})`
: `calc((100vw - 160px - 80px) / ${this.dateRange.length})`,
};
},
columnWrapperWidth() {
return this.expandedDisplayType
? {
width: `${380 * this.dateRange.length}px`,
}
: {
width: this.openSidebar
? "calc(100vw - 320px - 80px)"
: "calc(100vw - 160px - 80px)",
};
},
backgroundWrapperWidth() {
return {
width: `${380 * this.dateRange.length + 80}px`,
};
},
lineIndicatorLocation() {
return {
top: `${this.calculateIndicatorLocation()}px`,
};
},
circleIndicatorLocation() {
return {
top: `${this.calculateIndicatorLocation() + 42}px`,
};
},
},
methods: {
...mapActions({
getEvents: "getEvents",
}),
changeCurrentTime() {
this.currentTime = moment().format("HH:mm:ss");
},
timeCoilInitialization() {
this.timeCoil = [];
for (let i = this.validateStartTime; i < this.validateEndTime; i++) {
if (
i === this.hours &&
this.hours !== this.validateEndTime &&
this.isCurrentWeek
) {
this.timeCoil.push(this.hoursMinutes);
} else this.timeCoil.push(`${i}:00`);
}
},
changeTimeCoil() {
this.timeCoil = this.timeCoil.map((elem) => {
if (convertTime(elem, 0, -3) === this.hours) {
return this.hoursMinutes;
}
return elem.slice(0, -3) + ":00";
});
},
startTimer() {
if (
this.hours >= this.validateStartTime &&
this.hours < this.validateEndTime
) {
this.timer = setInterval(() => {
this.changeCurrentTime();
this.changeTimeCoil();
}, 5000);
}
},
stopTimer() {
clearInterval(this.timer);
this.timer = null;
},
calculateIndicatorLocation() {
let newTime = this.currentTime
.split(":")
.map((elem) => parseInt(elem, 10));
let result =
(newTime[0] - this.validateStartTime) * this.pixelsPerHour +
newTime[1] * this.pixelsPerMinute;
if (result > this.backgroundHeight || result < 0) {
this.isShownIndicator = false;
return 0;
}
return result;
},
},
watch: {
currentTime() {
if (
this.hours === this.validateEndTime &&
this.minutes > 0 &&
this.timer
) {
this.stopTimer();
this.timeCoilInitialization();
}
},
selectedDates: {
deep: true,
handler(newValue) {
this.isCurrentWeek =
newValue?.from.isSame(moment().clone().startOf("week")) &&
newValue?.to.isSame(moment().clone().endOf("week"));
this.isShownIndicator = this.isCurrentWeek;
if (this.timer) {
this.stopTimer();
this.timeCoilInitialization();
}
if (this.isCurrentWeek) {
this.changeCurrentTime();
this.timeCoilInitialization();
this.startTimer();
}
},
},
},
mounted() {
this.changeCurrentTime();
this.timeCoilInitialization();
this.startTimer();
this.getEvents();
},
beforeUnmount() {
this.stopTimer();
},
};
</script>
<style lang="sass" scoped>
.schedule-body
overflow-x: auto
border-top-left-radius: 4px
border-top-right-radius: 4px
&::-webkit-scrollbar-track:horizontal
margin: 0 24px 0 24px
.background
background: linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(9,9,121,1) 35%, rgba(0,212,255,1) 100%)
.time-coil-wrapper
position: sticky
z-index: 5
background-color: var(--default-white)
padding-top: 38px
.column-wrapper
height: 60px
position: relative
background-color: var(--default-white)
.footer
border-bottom-left-radius: 4px
border-bottom-right-radius: 4px
.border-bottom
border-bottom: 4px solid var(--bg-ligth-blue-color)
.records-count
border-right: 1px solid var(--border-light-grey-color)
&:last-child
border-right: none
.border-top
border-top: 1px solid var(--border-light-grey-color)
.time-circle-indicator
background-color: var(--bg-event-red-color)
z-index: 5
.time-line-indicator
border-top: 1px solid var(--bg-event-red-color)
z-index: 4
width: calc(100% - 80px)
</style>

View File

@@ -6,11 +6,12 @@
:choice-status="choiceStatus" :choice-status="choiceStatus"
v-model="time" v-model="time"
) )
base-input-with-search(v-model="patient", @createPerson="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
.wrapper-info.h-full .wrapper-info.h-full
.grid.grid-cols-2.gap-x-4.gap-y-6 .grid.grid-cols-2.gap-x-4.gap-y-6
base-input( base-input(
:disabled="!!patient.id"
v-model="phone", v-model="phone",
placeholder="+7 (915) 6449223", placeholder="+7 (915) 6449223",
mask="+7 (###) ###-##-##", mask="+7 (###) ###-##-##",
@@ -19,6 +20,7 @@
important important
) )
base-input-date( base-input-date(
:disabled="!!patient.id"
v-model="birth_date", v-model="birth_date",
size="M", size="M",
important, important,
@@ -29,6 +31,8 @@
.footer.flex.gap-2 .footer.flex.gap-2
base-button(type="secondary", label="Отменить", width="126px", @click="closeForm") base-button(type="secondary", label="Отменить", width="126px", @click="closeForm")
base-button(width="168px", label="Создать запись", @click="createEvent") base-button(width="168px", label="Создать запись", @click="createEvent")
base-modal(v-model="showCreateModal", title="Создать пациента", modal-padding)
patient-creation-form(@close="handleClosePatientForm")
</template> </template>
<script> <script>
@@ -41,6 +45,8 @@ import BaseInput from "@/components/base/BaseInput.vue";
import BaseInputDate from "@/components/base/BaseInputDate.vue"; import BaseInputDate from "@/components/base/BaseInputDate.vue";
import { fetchWrapper } from "@/shared/fetchWrapper"; import { fetchWrapper } from "@/shared/fetchWrapper";
import { mapActions } from "vuex"; import { mapActions } from "vuex";
import PatientCreationForm from "@/components/PatientCreationForm.vue";
import BaseModal from "@/components/base/BaseModal.vue";
export default { export default {
name: "CreateEventForm", name: "CreateEventForm",
@@ -51,29 +57,42 @@ export default {
BaseInputWithSearch, BaseInputWithSearch,
BaseInput, BaseInput,
BaseInputDate, BaseInputDate,
PatientCreationForm,
BaseModal,
}, },
props: { isShowForm: Boolean, closeForm: Function }, props: { isShowForm: Boolean, closeForm: Function },
data() { data() {
return { return {
patient: {}, patient: {},
phone: "", phone: "",
birth_date: {}, birth_date: "",
time: {}, time: {},
patientData: patientData, patientData: patientData,
currentStatus: patientData.statuses.find((e) => e.name === "Не принят"), currentStatus: patientData.statuses.find((e) => e.name === "Не принят"),
showCreateModal: false,
}; };
}, },
computed: {
primaryNumber() {
return this.patient?.contacts?.find((e) => e.category === "PHONE")?.value;
},
},
methods: { methods: {
...mapActions({ ...mapActions({
getEvents: "getEvents", getEvents: "getEvents",
}), }),
createPerson() { createPerson() {
alert("crea"); this.showCreateModal = true;
}, },
choiceStatus(e) { choiceStatus(e) {
this.currentStatus = e; this.currentStatus = e;
}, },
handleClosePatientForm(val) {
this.showCreateModal = false;
if (!val) return;
this.patient = val;
},
async createEvent() { async createEvent() {
const event = await fetchWrapper.post("events", { const event = await fetchWrapper.post("events", {
start: this.time.start.toISOString(), start: this.time.start.toISOString(),
@@ -81,7 +100,7 @@ export default {
person: { person: {
id: this.patient.id, id: this.patient.id,
}, },
medic_id: "a6195732-ac98-4f07-8916-4881eca69b7d", medic_id: this.$store.getters.getUserData.id,
}); });
if (event?.id) { if (event?.id) {
await this.getEvents(); await this.getEvents();
@@ -90,6 +109,16 @@ export default {
}, },
}, },
mounted() {}, mounted() {},
watch: {
patient(val) {
if (val.birth_date) {
this.birth_date = val.birth_date;
}
if (this.primaryNumber) {
this.phone = this.primaryNumber;
}
},
},
}; };
</script> </script>

View File

@@ -1,264 +0,0 @@
<template lang="pug">
.flex.flex-col.pt-6.gap-y-4(:style="{maxWidth: '682px'}")
header-record-form(
:current-status="currentStatus",
:statuses="patientData.statuses",
:choice-status="choiceStatus"
)
.flex.items-center.gap-x-3.text-smm
.text.font-semibold Жалобы:
.flex.gap-x-1
.services.flex.items-center.px-4.font-medium.text-smm.gap-x-1.h-9.rounded-md(
:class="{'other-serivices': services.length}"
)
.service.flex.gap-x-1.items-center.rounded.relative(
:style="{ width: otherColor ? '415px' : 'full'}"
)
span(v-if="!services.length") Выберите услуги
.dark-blue.flex.px-4.items-center.rounded.h-7(
id="service",
v-else,
v-for="service in services",
:style="{background: service.color}"
) {{service.title}}
.gradient.flex.absolute(
:style="{background: `linear-gradient(270deg, ${otherColor} 0%, rgba(247, 217, 255, 0) 100%)`}"
)
.other.flex.h-7.items-center.justify-center.rounded(v-if="otherColor") {{`+${otherServices.length}`}}
.price.flex.items-center.items-center.justify-center.rounded-md(v-if="services.length") {{sumService}}
q-btn.change.flex.w-7.h-9.rounded-md(@click="isShowServices = true", dense, padding="4px 4px")
q-icon.icon(name="app:folder", size="20px")
base-modal(v-model="isShowServices", title="Услуги", modal-padding)
services-modal(:close-services="closeServices", :save-services="saveServices")
base-input-full-name(:info-client="patientData")
.flex.flex-col.flex-auto.l.gap-y-8
.flex
button.title-info.px-6.py-2.cursor-pointer.w-full.text-sm(
v-for="form in forms",
@click="selectForm(form.component)",
:class="{active: form.component === currentForm}",
:key="form.id",
:id="form.id"
) {{form.title}}
component(
v-bind:is="currentForm",
:basic-info="patientData.basic",
:phone="patientData.phone",
:email="patientData.email",
:add-network="addNewNetwork",
:priority-list="patientData.priorityList",
:identity-document="patientData.identity_document",
:addresses="patientData.addresses",
:save-file="saveDocFile",
:networks-list="getNetworksList",
)
.footer.flex.gap-2
base-button(type="secondary", label="Отменить", width="126px", @click="closeForm")
base-button(width="168px", label="Создать запись", @click="closeForm", disabled)
</template>
<script>
import folder from "@/assets/icons/folder.svg";
import { column } from "@/pages/clients/utils/tableConfig";
import { v_model } from "@/shared/mixins/v-model";
import FormCreateBasicInfo from "@/pages/clients/components/FormCreateBasicInfo";
import FormCreateIdentityDocuments from "@/pages/clients/components/FormCreateIdentityDocuments";
import FormCreateAddresses from "@/pages/clients/components/FormCreateAddresses";
import FormCreateAttachments from "@/pages/clients/components/FormCreateAttachments.vue";
import BaseInputFullName from "@/components/base/BaseInputFullName.vue";
import { patientData } from "@/pages/newCalendar/utils/calendarConfig.js";
import HeaderRecordForm from "./HeaderRecordForm.vue";
import BaseCalendar from "@/components/base/Calendar/BaseCalendar.vue";
import BaseButton from "@/components/base/BaseButton.vue";
import BaseModal from "@/components/base/BaseModal.vue";
import ServicesModal from "./ServicesModal.vue";
export default {
name: "RecordCreationForm",
components: {
FormCreateBasicInfo,
FormCreateIdentityDocuments,
FormCreateAddresses,
FormCreateAttachments,
BaseInputFullName,
BaseCalendar,
HeaderRecordForm,
BaseButton,
BaseModal,
ServicesModal,
},
props: { isShowForm: Boolean, closeForm: Function },
mixins: [v_model],
data() {
return {
folder,
isPhoto: false,
patientData: patientData,
currentStatus: patientData.statuses.find((e) => e.name === "Не принят"),
services: [
{ id: 0, title: "Чистка зубов", price: 500, color: "#D8E3FF" },
{ id: 1, title: "Осмотр", price: 1300, color: "#FFF0CA" },
{
id: 2,
title: "Лечение среднего кариеса",
price: 500,
color: "#F7D9FF",
},
{ id: 3, title: "Осмотр зубов", price: 1300, color: "#FFF0CA" },
],
forms: [
{
title: "Основное",
id: "basic",
component: "form-create-basic-info",
},
{
title: "ДУЛ",
id: "doc",
component: "form-create-identity-documents",
},
{
title: "Адрес",
id: "address",
component: "form-create-addresses",
},
{
title: "Дополнительное",
id: "additional",
component: "form-create-attachments",
},
],
currentForm: "form-create-basic-info",
networksSettings: column.find((el) => el.name === "networks")?.settings,
otherColor: null,
otherServices: [],
isShowServices: false,
};
},
computed: {
getNetworksList() {
let contacts = [];
this.patientData.basic.contacts.forEach((elem) =>
contacts.push(elem.kind.id)
);
let filteredNetworks = this.networksSettings.filter(
({ id }) => !contacts.includes(id)
);
return filteredNetworks;
},
sumService() {
let sum = this.services.reduce((acc, el) => acc + el.price, 0) + " ₽";
return sum.length >= 5
? sum.replace(/(\d{1,3}(?=(?:\d\d\d)+(?!\d)))/g, "$1" + " ")
: sum;
},
},
methods: {
changePhoto() {
this.isPhoto = true;
},
selectForm(componentName) {
this.currentForm = this.forms.find(
(elem) => elem.component === componentName
)?.component;
},
saveDocFile(e) {
this.patientData.doc = e.target.files;
},
addNewNetwork() {
const newNetwork = this.getNetworksList;
if (newNetwork[0])
this.patientData.basic.contacts.push({
kind: {
id: newNetwork[0].id,
icon: newNetwork[0].icon,
},
username: "",
});
},
choiceStatus(e) {
this.currentStatus = e;
},
addShadow() {
let defaultWidth = 415;
const target = document.querySelectorAll("#service");
target.forEach((e) => {
defaultWidth = defaultWidth - e.offsetWidth;
if (defaultWidth <= 10) return this.otherServices.push(e);
});
this.otherColor = this.otherServices[0]?.style.background;
},
closeServices() {
this.isShowServices = false;
},
saveServices(selectedServices) {
this.isShowServices = false;
selectedServices.forEach((e) => this.services.push(e));
},
},
mounted() {
this.addShadow();
},
};
</script>
<style lang="sass" scoped>
.dark-blue
color: var(--font-dark-blue-color)
min-width: 50px
.text
color: var(--font-grey-color)
width: 76px
.change
background: var(--bg-light-grey)
color: var(--font-grey-color)
.services
background: var(--bg-light-grey)
color: var(--font-grey-color)
width: 560px
.service
display: -webkit-box
overflow: hidden
.gradient
width: 10%
height: 28px
right: 0
.other-serivices
width: 474px
padding: 4px
border: 1px solid var(--border-light-grey-color)
background: var(--default-white)
.other
width: 46px
background: var(--bg-light-grey)
.price
background: var(--bg-light-grey)
width: 82px
color: var(--font-grey-color)
.title-info
color: var(--font-grey-color)
border-bottom: 1.5px solid var(--font-grey-color)
&:hover
color: var(--btn-blue-color)
border-bottom: 1.5px solid var(--btn-blue-color)
&.active
color: var(--btn-blue-color)
border-bottom: 1.5px solid var(--btn-blue-color)
.footer
border-top: 1px solid var(--border-light-grey-color)
margin: 40px -32px 0px
padding: 16px 32px 0px 32px
.icon :deep(path)
fill: var(--font-grey-color)
</style>

View File

@@ -0,0 +1,49 @@
<template lang="pug">
.calendar-column-wrapper.flex.flex-col
.header.flex.flex-col.items-center.justify-center.top-0.pt-3.pb-10px
span.font-bold.text-base.color-black {{ date?.format("D MMMM")}}
span.text-smm.color-grey {{ capitalizeFirstChar(date?.format("dddd"))}}
.body.h-full.px-1.py-1
calendar-record-card(
v-for="record in filteredRecords",
:key="record?.id",
:record="record",
:expanded-type="expandedDisplayType",
:style="eventCardPosition(record.start, record.end)",
@click="changeSelectedRecordId(record?.id)",
)
.footer.flex.items-center.justify-center.bg-white.h-9(v-if="!expandedDisplayType")
span.text-smm.color-grey 0 записей
</template>
<script>
import * as moment from "moment/moment";
import { columnMixin } from "@/pages/newCalendar/mixins/columnMixin.js";
import { capitalizeFirstChar } from "@/pages/newCalendar/utils/calendarFunctions.js";
export default {
name: "CalendarColumn",
mixins: [columnMixin],
props: {
date: Object,
expandedDisplayType: Boolean,
},
data() {
return {
capitalizeFirstChar: capitalizeFirstChar,
};
},
computed: {
filteredRecords() {
return this.events.filter(
(elem) =>
moment.parseZone(elem.start).format("YYYY-MM-DD") ===
this.date.format("YYYY-MM-DD")
);
},
},
};
</script>
<style lang="sass" scoped>
@import "@/pages/newCalendar/sass/columnStyle.sass"
</style>

View File

@@ -22,10 +22,10 @@
) )
base-input.search( base-input.search(
size="M" size="M"
:width="350", :width="386",
v-model="currentWeek", v-model="currentWeek",
) )
.h-5.w-5.flex.items-center.justify-center .h-5.w-5.flex.items-center.justify-center.ml-3
q-icon.calendar-icon.text.cursor-pointer( q-icon.calendar-icon.text.cursor-pointer(
:name="calendarVisibility ? 'app:cancel-mini' : 'app:calendar'", :name="calendarVisibility ? 'app:cancel-mini' : 'app:calendar'",
size="20px", size="20px",
@@ -36,7 +36,7 @@
transition-show="scale", transition-show="scale",
transition-hide="scale" transition-hide="scale"
self="top middle", self="top middle",
:offset="[140, 8]" :offset="[160, 8]"
) )
base-calendar( base-calendar(
v-model="dates", v-model="dates",
@@ -54,32 +54,33 @@
dense, dense,
@click="nextWeek" @click="nextWeek"
) )
.h-10.p-1.flex.items-center.justify-between.bg-secondary.rounded.text-grey-color.ml-52 .h-10.p-1.flex.items-center.justify-between.bg-secondary.rounded.text-grey-color.ml-52.border
q-btn-toggle( q-btn-toggle(
v-model="value", v-model="value",
:options="displayTypesList", :options="displayTypesList",
no-caps, no-caps,
toggle-color="light-blue-4",
padding="0px" padding="0px"
) )
template(v-slot:one) template(v-slot:one)
.w-12.h-8.flex.items-center.justify-center .w-12.h-8.flex.items-center.justify-center.rounded(
:class="{'active-toggle': value === 'expanded'}"
)
date-switcher-svg(name-svg="expanded", :active="value === 'expanded'") date-switcher-svg(name-svg="expanded", :active="value === 'expanded'")
template(v-slot:two) template(v-slot:two)
.w-12.h-8.flex.items-center.justify-center .w-12.h-8.flex.items-center.justify-center.rounded(
:class="{'active-toggle': value === 'collapsed'}"
)
date-switcher-svg(name-svg="compressed", :active="value === 'collapsed'") date-switcher-svg(name-svg="compressed", :active="value === 'collapsed'")
</template> </template>
<script> <script>
import BaseCalendar from "@/components/base/Calendar/BaseCalendar.vue";
import { mapState, mapActions } from "vuex";
import { v_model } from "@/shared/mixins/v-model"; import { v_model } from "@/shared/mixins/v-model";
import DateSwitcherSvg from "@/pages/newCalendar/components/CalendarDateSwitcherSvg.vue"; import DateSwitcherSvg from "@/pages/newCalendar/components/CalendarDateSwitcherSvg.vue";
import BaseInput from "@/components/base/BaseInput.vue"; import { headerMixin } from "@/pages/newCalendar/mixins/headerMixin.js";
export default { export default {
name: "CalendarHeader", name: "CalendarHeader",
mixins: [v_model], mixins: [v_model, headerMixin],
components: { BaseInput, BaseCalendar, DateSwitcherSvg }, components: { DateSwitcherSvg },
data() { data() {
return { return {
displayTypesList: [ displayTypesList: [
@@ -96,33 +97,25 @@ export default {
from: null, from: null,
to: null, to: null,
}, },
calendarVisibility: false,
}; };
}, },
computed: { computed: {
currentWeek() { currentWeek() {
return `${this.dates?.from?.format( return `${this.dates?.from?.format(
"D MMMM YYYY" "D MMMM YYYY"
)} - ${this.dates?.to?.format("D MMMM YYYY")}`; )} ${this.dates?.to?.format("D MMMM YYYY")}`;
}, },
...mapState({
selectedDates: (state) => state.calendar.selectedDates,
}),
}, },
methods: { methods: {
previousWeek() { previousWeek() {
this.dates.from = this.dates.from.clone().subtract(1, "week"); const diff = this.dates?.to?.diff(this.dates?.from, "days") + 1;
this.dates.to = this.dates.to.clone().subtract(1, "week"); this.dates.from = this.dates.from.clone().subtract(diff, "day");
this.dates.to = this.dates.to.clone().subtract(diff, "day");
}, },
nextWeek() { nextWeek() {
this.dates.from = this.dates.from.clone().add(1, "week"); const diff = this.dates?.to?.diff(this.dates?.from, "days") + 1;
this.dates.to = this.dates.to.clone().add(1, "week"); this.dates.from = this.dates.from.clone().add(diff, "day");
}, this.dates.to = this.dates.to.clone().add(diff, "day");
...mapActions({
changeSelectedDates: "changeSelectedDates",
}),
saveDatesChange() {
this.calendarVisibility = false;
}, },
initializeDates() { initializeDates() {
this.dates = { this.dates = {
@@ -142,52 +135,23 @@ export default {
this.changeSelectedDates(val); this.changeSelectedDates(val);
}, },
}, },
}, selectedDates: {
mounted() { deep: true,
immediate: true,
handler(val) {
if (
!val?.from.isSame(this.dates?.from) ||
!val?.to.isSame(this.dates?.to)
)
this.initializeDates(); this.initializeDates();
}, },
},
},
}; };
</script> </script>
<style lang="sass" scoped> <style lang="sass" scoped>
.calendar-header-wrapper @import "@/pages/newCalendar/sass/headerStyle.sass"
background-color: var(--default-white)
height: 72px
border-radius: 4px
z-index: 10
.text
color: var(--font-dark-blue-color)
.text-grey-color
color: var(--font-grey-color)
.text-primary
color: var(--font-dark-blue-color-0) !important
.bg-secondary
background: var(--bg-light-grey) !important
.q-btn--round
width: 32px !important
height: 32px !important
min-width: 32px !important
min-height: 32px !important
.q-btn-group :deep(.q-btn-item)
border-radius: 4px !important
.search :deep(.q-field__marginal)
height: auto !important
.search :deep(.q-field__prepend)
padding-right: 6px !important
.search-icon :deep(path)
fill: var(--font-grey-color)
.calendar-icon :deep(path)
fill: var(--font-dark-blue-color)
</style> </style>
<style lang="sass"> <style lang="sass">
.q-field--outlined.q-field--readonly .q-field__control:before .q-field--outlined.q-field--readonly .q-field__control:before

View File

@@ -0,0 +1,135 @@
<template lang="pug">
.schedule.flex-1.flex.flex-col.relative(
:style="{width: openSidebar ? 'calc(100vw - 320px)' : 'calc(100vw - 160px)'}"
)
calendar-header.w-full.mb-1(v-model="displayType")
.schedule-body.h-full.bg-white.w-full(:class="{rounded: !expandedDisplayType}")
.column-wrapper.flex.ml-20(:style="columnWrapperWidth")
calendar-column(
v-for="date in dateRange",
:key="date",
:date="date",
:style="columnHeight",
:expanded-display-type="expandedDisplayType",
:day-start-time="validateStartTime"
:day-end-time="validateEndTime"
)
.flex.relative(
:style="expandedDisplayType ? backgroundWrapperWidth : {}",
:class="{'border-bottom': expandedDisplayType}"
)
.time-coil-wrapper.left-0.-mt-16
calendar-clock-column(
:time-coil="timeCoil",
:current-time="currentTime",
:is-current="isCurrent",
:day-end-time="validateEndTime"
)
.time-circle-indicator.left-74px.h-3.w-3.rounded-full.absolute(
v-if="isShownIndicator",
:style="circleIndicatorLocation"
)
span.time-line-indicator.block.left-20.absolute(
v-if="isShownIndicator",
:style="lineIndicatorLocation"
)
calendar-background.flex-1
.w-full.h-3.bg-white.footer(v-if="expandedDisplayType")
</template>
<script>
import CalendarHeader from "@/pages/newCalendar/components/doctorCalendar/CalendarHeader";
import CalendarColumn from "@/pages/newCalendar/components/doctorCalendar/CalendarColumn.vue";
import { mapState } from "vuex";
import * as moment from "moment/moment";
import { wrapperMixin } from "@/pages/newCalendar/mixins/wrapperMixin.js";
export default {
name: "CalendarWrapper",
components: {
CalendarHeader,
CalendarColumn,
},
props: {
openSidebar: Boolean,
},
mixins: [wrapperMixin],
data() {
return {
displayType: "expanded",
};
},
computed: {
...mapState({
selectedDates: (state) => state.calendar.selectedDates,
}),
expandedDisplayType() {
return this.displayType === "expanded";
},
backgroundWrapperWidth() {
return {
width: `${380 * this.dateRange.length + 80}px`,
};
},
dateRange() {
let diff = this.selectedDates?.to.diff(this.selectedDates?.from, "days");
let range = [];
for (let i = 0; i <= diff; i++) {
range.push(this.selectedDates?.from.clone().add(i, "day"));
}
return range;
},
columnHeight() {
return this.expandedDisplayType
? {
height: `${this.timeCoil.length * this.pixelsPerHour + 60}px`,
width: "380px",
}
: {
height: `${this.timeCoil.length * this.pixelsPerHour + 96}px`,
width: this.openSidebar
? `calc((100vw - 320px - 80px) / ${this.dateRange.length})`
: `calc((100vw - 160px - 80px) / ${this.dateRange.length})`,
};
},
columnWrapperWidth() {
return this.expandedDisplayType
? {
width: `${380 * this.dateRange.length}px`,
}
: {
width: this.openSidebar
? "calc(100vw - 320px - 80px)"
: "calc(100vw - 160px - 80px)",
};
},
},
watch: {
selectedDates: {
deep: true,
handler(newValue) {
this.isCurrent =
newValue?.from.isSame(moment().clone().startOf("week"), "day") &&
newValue?.to.isSame(moment().clone().endOf("week"), "day");
this.isShownIndicator = this.isCurrent;
if (this.timer) {
this.stopTimer();
this.timeCoilInitialization();
}
if (this.isCurrent) {
this.changeCurrentTime();
this.timeCoilInitialization();
this.startTimer();
}
},
},
},
mounted() {
this.changeCurrentTime();
this.timeCoilInitialization();
},
};
</script>
<style lang="sass" scoped>
@import "@/pages/newCalendar/sass/wrapperStyle.sass"
</style>

View File

@@ -0,0 +1,62 @@
<template lang="pug">
.calendar-column-wrapper.flex.flex-col
.header.flex.items-center.justify-between.top-0.py-2.px-6
.flex.h-full.items-center.h-11
q-avatar.mr-2(
size="40px",
round
)
img(:src="medic?.photo || defaultPhoto", alt="doctor")
.flex.flex-col.justify-between
span.text-dark {{trimName(medic?.last_name, medic?.first_name, medic?.patronymic)}}
span.text-xsx.color-grey {{medic?.job || "Терапевт"}}
button(@click="locked = !locked")
q-icon.lock-icon(
:name="locked ? 'app:lock' : 'app:lock-open'",
size="24px"
)
.body.h-full.px-1.py-1
calendar-record-card(
v-for="record in filteredRecords",
:key="record?.id",
:record="record",
:expanded-type="true",
:style="eventCardPosition(record.start, record.end)",
@click="changeSelectedRecordId(record?.id)",
)
</template>
<script>
import { columnMixin } from "@/pages/newCalendar/mixins/columnMixin.js";
import { trimName } from "@/pages/newCalendar/utils/calendarFunctions.js";
import doctorAvatar from "@/assets/images/doctor.png";
import { mapState } from "vuex";
export default {
name: "CalendarColumn",
mixins: [columnMixin],
props: {
medic: Object,
},
data() {
return {
trimName: trimName,
defaultPhoto: doctorAvatar,
locked: false,
};
},
computed: {
...mapState({
events: (state) => state.calendar.events,
}),
filteredRecords() {
return this.events.filter((elem) => elem?.medic?.ID === this.medic?.ID);
},
},
};
</script>
<style lang="sass" scoped>
@import "@/pages/newCalendar/sass/columnStyle.sass"
.lock-icon :deep(path)
fill: var(--font-grey-color)
</style>

View File

@@ -0,0 +1,140 @@
<template lang="pug">
.calendar-header-wrapper.w-full.flex.items-center.p-4.justify-between
base-input.search(
iconLeft,
:width="280",
size="M",
placeholder="Найти ...",
icon-left
)
template(v-slot:iconLeft)
q-icon.search-icon(name="app:search", size="20px")
.flex.gap-x-4.items-center.justify-center
q-btn(
color="secondary",
round,
size="14px",
dense,
icon="arrow_back_ios_new",
text-color="primary",
padding="2px 11px 2px 8px",
@click="previousDate",
)
base-input.search(
size="M"
:width="332",
v-model="currentDate",
:shadow-text="inputShadowText"
)
.h-5.w-5.flex.items-center.justify-center.ml-3
q-icon.calendar-icon.text.cursor-pointer(
:name="calendarVisibility ? 'app:cancel-mini' : 'app:calendar'",
size="20px",
)
q-menu(
:style="{'margin-top': '8px !important'}"
v-model="calendarVisibility",
transition-show="scale",
transition-hide="scale"
self="top middle",
:offset="[130, 8]"
)
base-calendar(
v-model="date",
:save="saveDatesChange",
:start-year="2000"
)
q-btn(
color="secondary",
icon="arrow_forward_ios",
round,
size="14px",
text-color="primary",
dense,
@click="nextDate"
)
.h-10.p-1.flex.items-center.justify-between.bg-secondary.rounded.text-grey-color.ml-52.border
q-btn-toggle(
v-model="value",
:options="displayTypesList",
no-caps,
padding="0px 12px"
)
</template>
<script>
import { v_model } from "@/shared/mixins/v-model";
import { headerMixin } from "@/pages/newCalendar/mixins/headerMixin.js";
import { capitalizeFirstChar } from "@/pages/newCalendar/utils/calendarFunctions.js";
export default {
name: "CalendarHeader",
mixins: [v_model, headerMixin],
data() {
return {
displayTypesList: [
{
value: "day",
label: "День",
},
{
value: "week",
label: "Неделя",
},
],
date: null,
};
},
computed: {
currentDate() {
return this.date?.format("D MMMM YYYY");
},
inputShadowText() {
return this.date?.isValid()
? capitalizeFirstChar(this.date?.format("dddd"))
: "";
},
},
methods: {
previousDate() {
this.date = this.date?.clone()?.subtract(1, "day");
},
nextDate() {
this.date = this.date?.clone()?.add(1, "day");
},
initializeDates() {
this.date = this.selectedDates.from.clone();
},
},
watch: {
date: {
deep: true,
handler(val) {
if (!val?.isSame(this.selectedDates?.from, "day"))
this.changeSelectedDates({
from: val,
to: val?.clone()?.endOf("day"),
});
},
},
selectedDates: {
deep: true,
immediate: true,
handler(val) {
if (!val?.from.isSame(this.date, "day")) this.initializeDates();
},
},
},
};
</script>
<style lang="sass" scoped>
@import "@/pages/newCalendar/sass/headerStyle.sass"
.q-btn-toggle :deep(span)
font-weight: 500
.q-btn-toggle :deep(.q-btn-item)
height: 32px
</style>
<style lang="sass">
.q-field--outlined.q-field--readonly .q-field__control:before
border-style: solid !important
</style>

View File

@@ -0,0 +1,132 @@
<template lang="pug">
.schedule.flex-1.flex.flex-col.relative(
:style="{width: openSidebar ? 'calc(100vw - 320px)' : 'calc(100vw - 160px)'}"
)
calendar-header.w-full.mb-1(v-model="displayType")
.schedule-body.h-full.bg-white.w-full
.column-wrapper.flex.ml-20(:style="columnWrapperWidth")
calendar-column(
v-for="medic in filteringMedics",
:key="medic?.id",
:medic="medic",
:style="columnStyle",
:expanded-display-type="true",
:day-start-time="validateStartTime"
:day-end-time="validateEndTime"
)
.flex.relative.border-bottom(
:style="backgroundWrapperWidth",
)
.time-coil-wrapper.left-0.-mt-16
calendar-clock-column(
:time-coil="timeCoil",
:current-time="currentTime",
:is-current="isCurrent",
:day-end-time="validateEndTime"
)
.time-circle-indicator.left-74px.h-3.w-3.rounded-full.absolute(
v-if="isShownIndicator",
:style="circleIndicatorLocation"
)
span.time-line-indicator.block.left-20.absolute(
v-if="isShownIndicator",
:style="lineIndicatorLocation"
)
calendar-background.flex-1
.w-full.h-3.bg-white.footer
</template>
<script>
import CalendarHeader from "@/pages/newCalendar/components/managerCalendar/CalendarHeader";
import CalendarColumn from "@/pages/newCalendar/components/managerCalendar/CalendarColumn.vue";
import { mapState } from "vuex";
import * as moment from "moment/moment";
import { wrapperMixin } from "@/pages/newCalendar/mixins/wrapperMixin.js";
export default {
name: "CalendarWrapper",
components: {
CalendarHeader,
CalendarColumn,
},
props: {
openSidebar: Boolean,
},
mixins: [wrapperMixin],
data() {
return {
displayType: "day",
};
},
computed: {
...mapState({
selectedDates: (state) => state.calendar.selectedDates,
events: (state) => state.calendar.events,
}),
filteringMedics() {
let medicsList = [];
this.events?.forEach(({ medic }) => {
if (!medicsList.includes(JSON.stringify(medic)))
medicsList.push(JSON.stringify(medic));
});
return medicsList?.map((elem) => JSON.parse(elem));
},
medicsListLength() {
return this.filteringMedics?.length;
},
columnStyle() {
return this.medicsListLength > 4
? {
height: `${this.timeCoil.length * this.pixelsPerHour + 60}px`,
width: "380px",
}
: {
height: `${this.timeCoil.length * this.pixelsPerHour + 60}px`,
width: `calc(100%/ ${this.medicsListLength})`,
};
},
backgroundWrapperWidth() {
return this.medicsListLength > 4
? {
width: `${380 * this.medicsListLength + 80}px`,
}
: {};
},
columnWrapperWidth() {
return this.medicsListLength > 4
? {
width: `${380 * this.medicsListLength}px`,
}
: {
width: "calc(100% - 80px)",
};
},
},
watch: {
selectedDates: {
immediate: true,
deep: true,
handler(newValue) {
this.isCurrent = newValue?.from.isSame(moment(), "day");
this.isShownIndicator = this.isCurrent;
if (this.timer) {
this.stopTimer();
this.timeCoilInitialization();
}
if (this.isCurrent) {
this.changeCurrentTime();
this.timeCoilInitialization();
this.startTimer();
}
},
},
},
mounted() {
this.changeCurrentTime();
this.timeCoilInitialization();
},
};
</script>
<style lang="sass" scoped>
@import "@/pages/newCalendar/sass/wrapperStyle.sass"
</style>

View File

@@ -0,0 +1,53 @@
import CalendarRecordCard from "@/pages/newCalendar/components/CalendarRecordCard.vue";
import { verifyTime } from "@/pages/newCalendar/utils/calendarFunctions.js";
import { mapActions, mapState } from "vuex";
export const columnMixin = {
props: {
dayEndTime: Number,
dayStartTime: Number,
},
emits: [],
components: { CalendarRecordCard },
data() {
return {
pixelsPerHour: 76,
};
},
computed: {
...mapState({
events: (state) => state.calendar.events || [],
}),
pixelsPerMinute() {
return this.pixelsPerHour / 60;
},
},
methods: {
...mapActions({
changeSelectedRecordId: "changeSelectedRecordId",
}),
eventCardPosition(startTime, endTime) {
let start = startTime
.slice(11, -4)
.split(":")
.map((elem) => parseInt(elem, 10));
let end = endTime.slice(11, -4);
let position =
(start[0] - this.dayStartTime) * this.pixelsPerHour +
start[1] * this.pixelsPerMinute +
4;
if (
parseInt(start[0], 10) < this.dayStartTime ||
verifyTime(end) > this.dayEndTime
) {
return {
top: "0px",
visibility: "hidden",
};
}
return {
top: `${position}px`,
};
},
},
};

View File

@@ -0,0 +1,26 @@
import BaseCalendar from "@/components/base/Calendar/BaseCalendar.vue";
import { mapState, mapActions } from "vuex";
import BaseInput from "@/components/base/BaseInput.vue";
export const headerMixin = {
components: { BaseInput, BaseCalendar },
data() {
return {
calendarVisibility: false,
};
},
computed: {
...mapState({
selectedDates: (state) => state.calendar.selectedDates,
}),
},
methods: {
...mapActions({
getEvents: "getEvents",
changeSelectedDates: "changeSelectedDates",
}),
saveDatesChange() {
this.calendarVisibility = false;
},
},
};

View File

@@ -0,0 +1,135 @@
import { mapState } from "vuex";
import * as moment from "moment/moment";
import CalendarBackground from "@/pages/newCalendar/components/CalendarBackground.vue";
import CalendarClockColumn from "@/pages/newCalendar/components/CalendarClockColumn.vue";
import {
convertTime,
verifyTime,
} from "@/pages/newCalendar/utils/calendarFunctions.js";
export const wrapperMixin = {
props: {
openSidebar: Boolean,
},
emits: [],
components: {
CalendarBackground,
CalendarClockColumn,
},
data() {
return {
currentTime: "",
isShownIndicator: true,
timeCoil: [],
timer: null,
pixelsPerHour: 76,
isCurrent: true,
};
},
computed: {
...mapState({
workingHours: (state) => state.calendar.workingHours,
}),
hours() {
return convertTime(this.currentTime, 0, -6);
},
minutes() {
return convertTime(this.currentTime, 3, -3);
},
hoursMinutes() {
return this.currentTime.slice(0, -3);
},
pixelsPerMinute() {
return this.pixelsPerHour / 60;
},
validateStartTime() {
return verifyTime(this.workingHours.start);
},
validateEndTime() {
return verifyTime(this.workingHours.end);
},
backgroundHeight() {
return (
(this.validateEndTime - this.validateStartTime) * this.pixelsPerHour
);
},
lineIndicatorLocation() {
return {
top: `${this.calculateIndicatorLocation()}px`,
};
},
circleIndicatorLocation() {
return {
top: `${this.calculateIndicatorLocation() + 58}px`,
};
},
},
methods: {
changeCurrentTime() {
this.currentTime = moment().format("HH:mm:ss");
},
changeTimeCoil() {
this.timeCoil = this.timeCoil.map((elem) => {
if (convertTime(elem, 0, -3) === this.hours) {
return this.hoursMinutes;
}
return elem.slice(0, -3) + ":00";
});
},
startTimer() {
if (
this.hours >= this.validateStartTime &&
this.hours < this.validateEndTime
) {
this.timer = setInterval(() => {
this.changeCurrentTime();
this.changeTimeCoil();
}, 5000);
}
},
stopTimer() {
clearInterval(this.timer);
this.timer = null;
},
calculateIndicatorLocation() {
let newTime = this.currentTime
.split(":")
.map((elem) => parseInt(elem, 10));
let result =
(newTime[0] - this.validateStartTime) * this.pixelsPerHour +
newTime[1] * this.pixelsPerMinute;
if (result > this.backgroundHeight || result < 0) {
this.isShownIndicator = false;
return 0;
}
return result;
},
timeCoilInitialization() {
this.timeCoil = [];
for (let i = this.validateStartTime; i < this.validateEndTime; i++) {
if (
i === this.hours &&
this.hours !== this.validateEndTime &&
this.isCurrent
) {
this.timeCoil.push(this.hoursMinutes);
} else this.timeCoil.push(`${i}:00`);
}
},
},
watch: {
currentTime() {
if (
this.hours === this.validateEndTime &&
this.minutes > 0 &&
this.timer
) {
this.stopTimer();
this.timeCoilInitialization();
}
},
},
beforeUnmount() {
this.stopTimer();
},
};

View File

@@ -0,0 +1,24 @@
.calendar-column-wrapper
border-right: 1px solid var(--border-light-grey-color)
&:last-child
border-right: none
.header
position: sticky
z-index: 5
background-color: var(--default-white)
.body
position: relative
z-index: 3
.color-black
color: var(--font-dark-blue-color)
.color-grey
color: var(--font-grey-color)
.footer
border-top: 1px solid var(--border-light-grey-color)
@media(min-height: 1090px)
border-bottom: 1px solid var(--border-light-grey-color)

View File

@@ -0,0 +1,41 @@
.calendar-header-wrapper
background-color: var(--default-white)
height: 72px
border-radius: 4px
z-index: 10
.text
color: var(--font-dark-blue-color)
.text-grey-color
color: var(--font-grey-color)
.text-primary
color: var(--font-dark-blue-color-0) !important
.bg-secondary
background: var(--bg-light-grey) !important
.q-btn--round
width: 32px !important
height: 32px !important
min-width: 32px !important
min-height: 32px !important
.q-btn-group :deep(.q-btn-item)
border-radius: 4px !important
.search :deep(.q-field__marginal)
height: auto !important
.search :deep(.q-field__prepend)
padding-right: 6px !important
.search-icon :deep(path)
fill: var(--font-grey-color)
.calendar-icon :deep(path)
fill: var(--font-dark-blue-color)
.border-toggle
border: 1px solid var(--gray-secondary)

View File

@@ -0,0 +1,44 @@
.schedule-body
overflow-x: auto
border-top-left-radius: 4px
border-top-right-radius: 4px
&::-webkit-scrollbar-track:horizontal
margin: 0 24px 0 24px
.background
background: linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(9,9,121,1) 35%, rgba(0,212,255,1) 100%)
.time-coil-wrapper
position: sticky
z-index: 5
background-color: var(--default-white)
padding-top: 52px
.column-wrapper
height: 60px
position: relative
background-color: var(--default-white)
.footer
border-bottom-left-radius: 4px
border-bottom-right-radius: 4px
.border-bottom
border-bottom: 4px solid var(--bg-ligth-blue-color)
.records-count
border-right: 1px solid var(--border-light-grey-color)
&:last-child
border-right: none
.border-top
border-top: 1px solid var(--border-light-grey-color)
.time-circle-indicator
background-color: var(--bg-event-red-color)
z-index: 5
.time-line-indicator
border-top: 1px solid var(--bg-event-red-color)
z-index: 4
width: calc(100% - 80px)

View File

@@ -48,10 +48,29 @@ export const statuses = [
label: "Не принят", label: "Не принят",
icon: not_accepted, icon: not_accepted,
value: "not_accepted", value: "not_accepted",
class: "not-accepted",
},
{
id: 1,
label: "Принят",
icon: accepted,
value: "accepted",
class: "accepted",
},
{
id: 2,
label: "Отказ",
icon: rejected,
value: "rejected",
class: "rejected",
},
{
id: 3,
label: "На приеме",
icon: reception,
value: "reception",
class: "reception",
}, },
{ id: 1, label: "Принят", icon: accepted, value: "accepted" },
{ id: 2, label: "Отказ", icon: rejected, value: "rejected" },
{ id: 3, label: "На приеме", icon: reception, value: "reception" },
], ],
}, },
{ {
@@ -759,16 +778,16 @@ export const services = [
export const recordList = [ export const recordList = [
{ {
id: "c3df0a95-8093-41f1-9584-5b70ee05e71c", id: "asdf0a95-8093-41f1-9584-5b70ee05e71c",
start: "2023-07-05T10:00:00Z", start: "2023-09-18T10:00:00Z",
end: "2023-07-05T11:00:00Z", end: "2023-09-18T11:00:00Z",
employee: { employee: {
id: "464101e6-b4e6-46a4-8ef6-08ecb2921493", id: "464101e6-b4e6-46a4-8ef6-08ecb2921493",
last_name: "Каневский", last_name: "Каневский",
first_name: "Леонид", first_name: "Леонид",
patronymic: "Семенович", patronymic: "Семенович",
}, },
member: { person: {
id: "3e6e54e4-2706-47d3-8b54-b841ee8f0943", id: "3e6e54e4-2706-47d3-8b54-b841ee8f0943",
last_name: "Харитонова", last_name: "Харитонова",
first_name: "Ольга", first_name: "Ольга",
@@ -804,16 +823,16 @@ export const recordList = [
], ],
}, },
{ {
id: "ba931000-7140-4977-bd09-1ac212b8b8e5", id: "ss931000-7140-4977-bd09-1ac212b8b8e5",
start: "2023-07-06T15:00:00Z", start: "2023-09-18T15:00:00Z",
end: "2023-07-06T15:30:00Z", end: "2023-09-18T15:30:00Z",
employee: { employee: {
id: "464101e6-b4e6-46a4-8ef6-08ecb2921493", id: "464101e6-b4e6-46a4-8ef6-08ecb2921493",
last_name: "Каневский", last_name: "Каневский",
first_name: "Леонид", first_name: "Леонид",
patronymic: "Семенович", patronymic: "Семенович",
}, },
member: { person: {
id: "d340d344-4fc7-4fbe-9db8-b7562b70438d", id: "d340d344-4fc7-4fbe-9db8-b7562b70438d",
last_name: "Гаранова", last_name: "Гаранова",
first_name: "Наталья", first_name: "Наталья",
@@ -858,16 +877,16 @@ export const recordList = [
], ],
}, },
{ {
id: "4cd94bec-a0df-4a18-879d-f64cd6d7098e", id: "ccd94bec-a0df-4a18-879d-f64cd6d5698e",
start: "2023-07-03T13:00:00Z", start: "2023-09-18T13:00:00Z",
end: "2023-07-03T14:30:00Z", end: "2023-09-18T14:30:00Z",
employee: { employee: {
id: "464101e6-b4e6-46a4-8ef6-08ecb2921493", id: "111101e6-b4e6-46a4-8ef6-08ecb2921493",
last_name: "Каневский", last_name: "Гурцев",
first_name: "Леонид", first_name: "Семен",
patronymic: "Семенович", patronymic: "Семенович",
}, },
member: { person: {
id: "0b2d1db1-6aab-4e29-b857-fc7c9777280f", id: "0b2d1db1-6aab-4e29-b857-fc7c9777280f",
last_name: "Харитонова", last_name: "Харитонова",
first_name: "Ольга", first_name: "Ольга",
@@ -911,6 +930,105 @@ export const recordList = [
}, },
], ],
}, },
{
id: "vv931000-7140-4977-bd09-1ac233b8b8e5",
start: "2023-09-18T15:00:00Z",
end: "2023-09-18T15:30:00Z",
employee: {
id: "333101e6-b4e6-46a4-8ef6-08ecb2921493",
last_name: "Лебедев",
first_name: "Леонид",
patronymic: "Семенович",
},
person: {
id: "d340d344-4fc7-4fbe-9db8-b7562b70438d",
last_name: "Гаранова",
first_name: "Наталья",
patronymic: "Романовна",
birth_date: "1990-04-12",
photo: personImage,
contacts: [
{
value: "+7 (910) 4241313",
kind: "PHONE",
},
{
value: "Haritonich@mail.ru",
kind: "EMAIL",
},
],
},
status: "accepted",
medicalCard: {
status: "not_filled",
},
},
{
id: "rr931000-7140-4977-bd09-1ac233b8b8e5",
start: "2023-09-18T15:00:00Z",
end: "2023-09-18T15:30:00Z",
employee: {
id: "666101e6-b4e6-46a4-8ef6-08ecb7721493",
last_name: "Петров",
first_name: "Леонид",
patronymic: "Семенович",
},
person: {
id: "d340d344-4fc7-4fbe-9db8-b7562b70438d",
last_name: "Гаранова",
first_name: "Наталья",
patronymic: "Романовна",
birth_date: "1990-04-12",
photo: personImage,
contacts: [
{
value: "+7 (910) 4241313",
kind: "PHONE",
},
{
value: "Haritonich@mail.ru",
kind: "EMAIL",
},
],
},
status: "accepted",
medicalCard: {
status: "not_filled",
},
},
{
id: "ma931000-7140-4977-bd09-1ac233b8b8e5",
start: "2023-09-18T15:00:00Z",
end: "2023-09-18T15:30:00Z",
employee: {
id: "999101e6-b4e6-46a4-8ef6-22ecb2921493",
last_name: "Ленин",
first_name: "Леонид",
patronymic: "Семенович",
},
person: {
id: "d340d344-4fc7-4fbe-9db8-b7562b70438d",
last_name: "Гаранова",
first_name: "Наталья",
patronymic: "Романовна",
birth_date: "1990-04-12",
photo: personImage,
contacts: [
{
value: "+7 (910) 4241313",
kind: "PHONE",
},
{
value: "Haritonich@mail.ru",
kind: "EMAIL",
},
],
},
status: "accepted",
medicalCard: {
status: "not_filled",
},
},
]; ];
export const recordPreviewConfig = [ export const recordPreviewConfig = [

View File

@@ -3,8 +3,8 @@ export function convertTime(str, startIndex, endIndex) {
} }
export function trimName(lastName, firstName, patronymic) { export function trimName(lastName, firstName, patronymic) {
let checkedFirstName = firstName !== null ? firstName?.[0] + ". " : ""; let checkedFirstName = firstName ? firstName?.[0] + ". " : "";
let checkedPatronymic = patronymic !== null ? patronymic?.[0] + "." : ""; let checkedPatronymic = patronymic ? patronymic?.[0] + "." : "";
return `${lastName} ${checkedFirstName}${checkedPatronymic}`; return `${lastName} ${checkedFirstName}${checkedPatronymic}`;
} }
@@ -13,3 +13,7 @@ export function verifyTime(dayTime) {
let newTime = timeArray[1] >= 30 ? timeArray[0] + 1 : timeArray[0]; let newTime = timeArray[1] >= 30 ? timeArray[0] + 1 : timeArray[0];
return newTime; return newTime;
} }
export function capitalizeFirstChar(str) {
return str[0]?.toUpperCase() + str?.slice(1);
}

View File

@@ -1,20 +1,10 @@
<template lang="pug"> <template lang="pug">
.w-full.h-full.flex.flex-col .w-full.fit.row.no-wrap.justify-evenly
.flex.w-full.gap-x-2.pb-2 .col-grow.pr-2
medical-header( medical-card-patient-form
v-model="currentMenuItem", medical-base-info.mt-2
:change-shown-remove-modal="changeShownRemoveModal", .h-full
) the-right-menu
medical-records
component(
v-if="currentMenuItem",
v-bind:is="currentMenuItem",
)
base-modal(
v-model="isShownRemoveModal",
title="Удаление медкарты"
)
medical-remove-modal(:change-shown-remove-modal="changeShownRemoveModal")
</template> </template>
<script> <script>
@@ -24,6 +14,9 @@ import MedicalBaseInfo from "@/pages/newMedicalCard/components/MedicalBaseInfo.v
import BaseModal from "@/components/base/BaseModal.vue"; import BaseModal from "@/components/base/BaseModal.vue";
import MedicalRemoveModal from "@/pages/newMedicalCard/components/MedicalRemoveModal.vue"; import MedicalRemoveModal from "@/pages/newMedicalCard/components/MedicalRemoveModal.vue";
import { mapState } from "vuex"; import { mapState } from "vuex";
import TheRightMenu from "@/components/TheRightMenu.vue";
import MedicalCardPatientForm from "@/pages/newMedicalCard/components/MedicalCardPatientForm.vue";
export default { export default {
name: "TheMedicalCard", name: "TheMedicalCard",
components: { components: {
@@ -32,6 +25,8 @@ export default {
MedicalBaseInfo, MedicalBaseInfo,
BaseModal, BaseModal,
MedicalRemoveModal, MedicalRemoveModal,
TheRightMenu,
MedicalCardPatientForm,
}, },
data() { data() {
return { return {
@@ -52,3 +47,5 @@ export default {
}, },
}; };
</script> </script>
<style lang="sass" scoped></style>

View File

@@ -46,7 +46,7 @@
padding="0", padding="0",
@click="(e) => addNewAllergy(e)" @click="(e) => addNewAllergy(e)"
) )
q-icon(name="app:icon-plus", size="12px", left) q-icon.icon(name="app:plus", size="24px", left)
span Добавить span Добавить
</template> </template>
@@ -158,7 +158,9 @@ export default {
.label-field .label-field
color: var(--font-grey-color) color: var(--font-grey-color)
.on-left .on-left
margin-right: 10px margin-right: 4px
.q-btn :deep(.q-ripple) .q-btn :deep(.q-ripple)
display: none display: none
.icon :deep(path)
fill: var(--btn-blue-color)
</style> </style>

View File

@@ -1,5 +1,6 @@
<template lang="pug"> <template lang="pug">
medical-form-wrapper( medical-form-wrapper(
id="base"
title="Основные данные" title="Основные данные"
:is-check-change="isCheckChange" :is-check-change="isCheckChange"
:is-loading-data="isLoadingData" :is-loading-data="isLoadingData"
@@ -8,12 +9,12 @@
:cancel="cancelEdit" :cancel="cancelEdit"
:open-edit="openEdit" :open-edit="openEdit"
) )
q-form.form-wrap.gap-24.w-full(ref="formBasicData") q-form.form-wrap.gap-6.w-full(ref="formBasicData")
.flex.flex-col.gap-4(v-for="data in configData") .flex.flex-col.gap-2(v-for="data in configData")
.font-semibold.text-sm.whitespace-nowrap {{data.dataLabel}} .font-semibold.text-sm.whitespace-nowrap {{data.dataLabel}}
.flex.w-full.justify-between.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} :`}}
.avatar-field.flex.gap-3.items-center(v-if="field.type === 'avatar'") .flex.gap-3.items-center.h-10(v-if="field.type === 'avatar'")
base-avatar(:size="40") base-avatar(:size="40")
img.avatar.object-cover( img.avatar.object-cover(
v-if="basic[data.dataKey][field.key]?.photo" v-if="basic[data.dataKey][field.key]?.photo"
@@ -37,15 +38,16 @@
:standout="!isEdit" :standout="!isEdit"
:items="genderOptions" :items="genderOptions"
bg-color="#e9e9f6", bg-color="#e9e9f6",
width="100%",
size="M" size="M"
) )
base-input-date( base-input-date(
v-else-if="field.type === 'date'", v-else-if="field.type === 'date'",
v-model="basic[data.dataKey][field.key]", v-model="basic[data.dataKey][field.key]",
size="M", size="M",
readonly width="100%",
@update:model-value="checkChangeInput", readonly,
:width="302" @update:model-value="checkChangeInput"
) )
base-input( base-input(
v-else, v-else,
@@ -54,7 +56,7 @@
@update:model-value="checkChangeInput" @update:model-value="checkChangeInput"
:type="field.type" :type="field.type"
:name="field.key" :name="field.key"
:width="302", width="100%",
size="M", size="M",
:rule="field.rules" :rule="field.rules"
) )
@@ -114,6 +116,7 @@ export default {
basic: (state) => state.medical.basicData, basic: (state) => state.medical.basicData,
isNew: (state) => state.medical.medicalCard.type === "new", isNew: (state) => state.medical.medicalCard.type === "new",
medicalCard: (state) => state.medical.medicalCard, medicalCard: (state) => state.medical.medicalCard,
addresses: (state) => state.medical.medicalCard.person.addresses,
}), }),
}, },
methods: { methods: {
@@ -133,21 +136,21 @@ export default {
} }
Object.keys(this.basic).map((key) => { Object.keys(this.basic).map((key) => {
if (key === "registrationAddress") if (key === "registrationAddress")
this.getRequestChangeData( this.postAdresses(
this.basic.registrationAddress, this.basic.registrationAddress,
this.initDataBasic[key], this.initDataBasic[key],
"Адрес регистрации" "REGISTRATION_ADDRESS"
); );
if (key === "residenceAddress") if (key === "residenceAddress")
this.getRequestChangeData( this.postAdresses(
this.basic.residenceAddress, this.basic.residenceAddress,
this.initDataBasic[key], this.initDataBasic[key],
"Адрес проживания" "CURRENT_ADDRESS"
); );
}); });
this.isEdit = false; this.isEdit = false;
}, },
getRequestChangeData(updateData, initData, key) { postAdresses(updateData, initData, key) {
let isInitDataEmpty = [...Object.keys(removeEmptyFields(initData))] let isInitDataEmpty = [...Object.keys(removeEmptyFields(initData))]
.length; .length;
if (Object.keys(removeEmptyFields(updateData)).length) { if (Object.keys(removeEmptyFields(updateData)).length) {
@@ -159,7 +162,7 @@ export default {
}); });
if (JSON.stringify(updateData) !== JSON.stringify(initData)) if (JSON.stringify(updateData) !== JSON.stringify(initData))
return this.updateAddress({ return this.updateAddress({
id: this.basic.personalData.id, id: this.addresses.find(({ category }) => category === key).id,
address: updateData, address: updateData,
category: key, category: key,
}); });
@@ -181,22 +184,10 @@ export default {
const form = this.$refs.formBasicData; const form = this.$refs.formBasicData;
form.validate().then((validate) => { form.validate().then((validate) => {
if (validate) { if (validate) {
let person = this.basic.personalData; this.updateBasicData();
this.$store.dispatch("setMedicalCardData", { this.isLoadingData = false;
...this.medicalCard, this.isEdit = false;
person: { this.isCheckChange = false;
...this.medicalCard.person,
...person,
},
});
//let updateBasic = this.updateBasicData();
// Promise.allSettled(updateBasic)
// .then(() => this.$store.dispatch("getMedicalCardData"))
// .then(() => {
// this.isLoadingData = false;
// this.isEdit = false;
// this.isCheckChange = false;
// });
} else { } else {
getFieldsNameUnvalidated(form).forEach((errorKey) => { getFieldsNameUnvalidated(form).forEach((errorKey) => {
addNotification( addNotification(
@@ -222,9 +213,7 @@ export default {
<style lang="sass" scoped> <style lang="sass" scoped>
.select .select
width: 302px width: 100%
.avatar-field
min-width: 302px
.avatar .avatar
height: 100% height: 100%
border-radius: 50% border-radius: 50%
@@ -234,12 +223,10 @@ export default {
display: grid display: grid
grid-template-columns: repeat(3, 1fr) grid-template-columns: repeat(3, 1fr)
grid-template-rows: repeat(1, 1fr) grid-template-rows: repeat(1, 1fr)
@media(max-width: 1900px) @media(max-width: 1440px)
grid-template-columns: repeat(2, 1fr) grid-template-columns: repeat(2, 1fr)
grid-template-rows: repeat(2, 1fr) grid-template-rows: repeat(2, 1fr)
@media(max-width: 1320px)
grid-template-columns: repeat(1, 1fr)
grid-template-rows: repeat(3, 1fr)
.label-field .label-field
min-width: 110px
color: var(--font-grey-color) color: var(--font-grey-color)
</style> </style>

View File

@@ -1,5 +1,6 @@
<template lang="pug"> <template lang="pug">
medical-form-wrapper( medical-form-wrapper(
id="contacts"
title="Контакты" title="Контакты"
:is-check-change="isCheckChange" :is-check-change="isCheckChange"
:is-loading-data="isLoadingData" :is-loading-data="isLoadingData"
@@ -8,35 +9,36 @@
:cancel="cancelEdit" :cancel="cancelEdit"
:open-edit="openEdit" :open-edit="openEdit"
) )
q-form.form-wrap.gap-24.w-full(ref="formContacts") q-form.form-wrap.gap-6.w-full(ref="formContacts")
.flex.flex-col.gap-4(v-for="(contact, key) in contacts") .flex.flex-col.gap-2(v-for="(contact, key) in contacts")
.font-semibold.text-sm.whitespace-nowrap {{configData[key].title}} .font-semibold.text-sm.whitespace-nowrap {{configData[key].title}}
.flex.w-full.justify-between.items-center.gap-4(v-for="(data, index) in contact") .flex.w-full.items-center.gap-4(v-for="(data, index) in contact")
.label-field.font-sm.text-sm.whitespace-nowrap {{`${configData[key].fieldName} ${index+1} :`}} .label-field.font-sm.text-sm.whitespace-nowrap {{`${configData[key].fieldName} ${index+1} :`}}
.input-wrapper.items-center.flex.h-10.gap-10px .w-full.items-center.flex.h-10
.flex(v-if="key === 'networks'") .flex.w-full(v-if="key === 'networks'")
.flex.rounded-full.cursor-pointer.items-center.gap-10px.py-1.pl-1.pr-2( .flex.rounded-full.cursor-pointer.items-center.gap-10px.py-1.pl-1.pr-2(
v-if="data.id || data.filled" v-if="data.id || data.filled"
@click="() => copyLinkNetwork(data.value)" @click="() => copyLinkNetwork(data.value)"
style="background-color: var(--bg-light-grey)" style="background-color: var(--bg-light-grey)"
) )
q-icon.icon(:name="mapNetworks[data.kind].icon" :class="{old: data.id, new: data.filled}") q-icon.icon(:name="mapNetworks[data.category].icon" :class="{old: data.id, new: data.filled}")
span.text-sm.font-medium {{ mapNetworks[data.kind].title || data.value }} span.text-sm.font-medium {{ mapNetworks[data.category].title || data.value }}
.flex.gap-10px(v-else v-click-outside="() => checkNetworksField(index)" :key="key+index") .flex.gap-10px.w-full(v-else v-click-outside="() => checkNetworksField(index)" :key="key+index")
base-select-networks( base-select-networks(
:list-data="Object.values(mapNetworks)" :list-data="Object.values(mapNetworks)"
:option-data="data.kind" :option-data="data.category"
:choose-option="(e) => chooseNetwork(e, index)" :choose-option="(e) => chooseNetwork(e, index)"
:style-border="true" :style-border="true"
:size-input="50" :size-input="50"
) )
base-input.w-full( base-input(
v-model="data.value", v-model="data.value",
:name="key", :name="key",
:rule="configData[key].rules", :rule="configData[key].rules",
size="M" size="M",
width="100%"
) )
base-input.w-full( base-input(
v-else, v-else,
:readonly="!isEdit", :readonly="!isEdit",
v-model="data.value", v-model="data.value",
@@ -44,23 +46,25 @@
:mask="configData[key].inputMask", :mask="configData[key].inputMask",
:rule="configData[key].rules", :rule="configData[key].rules",
@update:model-value="checkChangeInput", @update:model-value="checkChangeInput",
size="M" size="M",
width="100%"
) )
.delete-contact.icon-cancel.text-xxs.cursor-pointer( q-icon.delete-icon.ml-2.cursor-pointer(
v-if="isEdit" v-if="isEdit"
@click="() => deleteContact(key, index)" @click="() => deleteContact(key, index)"
name="app:cancel-mini",
size="24px"
) )
q-btn.ml-2px( q-btn.ml-2px(
v-if="isEdit", v-if="isEdit",
color="primary", color="primary",
flat, flat,
:style="{'font-weight': 500, width: '104px', height: '24px'}", :style="{'font-weight': 500, width: '76px', height: '40px'}",
no-caps, no-caps,
padding="0", padding="0",
@click="(e) => addNewContact(e, key)" @click="(e) => addNewContact(e, key)"
label="Добавить"
) )
q-icon(name="app:icon-plus", size="12px", left)
span Добавить
</template> </template>
<script> <script>
@@ -74,9 +78,9 @@ import {
getFieldsNameUnvalidated, getFieldsNameUnvalidated,
} from "@/shared/utils/changesObjects"; } from "@/shared/utils/changesObjects";
import { contactsDataForm } from "@/pages/newMedicalCard/utils/medicalConfig"; import { contactsDataForm } from "@/pages/newMedicalCard/utils/medicalConfig";
import { getRequestArrayData } from "@/shared/utils/wrapperRequestChangeData";
import { mapState, mapGetters } from "vuex"; import { mapState, mapGetters } from "vuex";
import BaseInput from "@/components/base/BaseInput.vue"; import BaseInput from "@/components/base/BaseInput.vue";
import { mapActions } from "vuex";
export default { export default {
name: "ContactsForm", name: "ContactsForm",
@@ -106,13 +110,14 @@ export default {
}), }),
}, },
methods: { methods: {
...mapActions({
createContact: "postCreateContact",
updateContact: "postUpdateContact",
delContacts: "deleteContact",
}),
checkNetworksField(index) { checkNetworksField(index) {
if ( if (this.contacts.networks[index].category)
this.contacts.networks[index].kind &&
this.contacts.networks[index].username
) {
this.contacts.networks[index].filled = true; this.contacts.networks[index].filled = true;
}
}, },
openEdit() { openEdit() {
this.isEdit = true; this.isEdit = true;
@@ -123,35 +128,46 @@ export default {
this.isCheckChange = false; this.isCheckChange = false;
}, },
updateContacts() { updateContacts() {
this.isLoadingData = true;
const allNewContacts = [ const allNewContacts = [
...this.contacts.phones, ...this.contacts.phones,
...this.contacts.emails, ...this.contacts.emails,
...this.contacts.networks, ...this.contacts.networks,
]; ];
const notEmptyContacts = allNewContacts.filter( const notEmptyContacts = allNewContacts.filter((el) => el.category);
(el) => el.kind && el.username return this.changeContacts(this.initAllContacts, notEmptyContacts);
); },
return getRequestArrayData( changeContacts(initData, notEmptyContacts) {
this.initAllContacts, const deleteRequests = initData
notEmptyContacts, .filter((el) => !notEmptyContacts.find((data) => data.id === el.id))
"person", .map((el) => this.delContacts({ id: el.id }));
this.personId, const requests = notEmptyContacts.map((el) => {
"general", const requestObj = {
"contact" category: el.category,
); value: el.value,
};
delete requestObj.id;
if (!el?.id)
return this.createContact({ obj: requestObj, id: this.personId });
if (
el?.id &&
checkChangeData(
initData.find((obj) => obj.id === el.id),
el
)
)
return this.updateContact({ obj: requestObj, id: el.id });
});
return requests.concat(deleteRequests);
}, },
saveChange() { saveChange() {
const form = this.$refs.formContacts; const form = this.$refs.formContacts;
form.validate().then((validate) => { form.validate().then((validate) => {
if (validate) { if (validate) {
this.isLoadingData = true; this.updateContacts();
Promise.allSettled(this.updateContacts())
.then(() => this.$store.dispatch("getMedicalCardData"))
.then(() => {
this.isLoadingData = false; this.isLoadingData = false;
this.isEdit = false; this.isEdit = false;
this.isCheckChange = false; this.isCheckChange = false;
});
} else { } else {
getFieldsNameUnvalidated(form).forEach((errorKey) => { getFieldsNameUnvalidated(form).forEach((errorKey) => {
addNotification( addNotification(
@@ -172,27 +188,25 @@ export default {
); );
}, },
chooseNetwork(e, index) { chooseNetwork(e, index) {
this.contacts.networks[index].kind = e.currentTarget.id; this.contacts.networks[index].category = e.currentTarget.id;
}, },
addNewContact(e, key) { addNewContact(e, key) {
if (e.pointerType) { if (e.pointerType) {
if (key === "networks") { if (key === "networks") {
this.contacts[key].push({ this.contacts[key].push({
filled: false, filled: false,
kind: this.configData[key]?.kind, category: this.configData[key]?.category,
username: null,
}); });
} else { } else {
this.contacts[key].push({ this.contacts[key].push({
kind: this.configData[key]?.kind, category: this.configData[key]?.category,
username: null,
}); });
} }
} }
this.checkChangeInput(); this.checkChangeInput();
}, },
deleteContact(key, index) { deleteContact(key, index) {
this.contacts[key].splice(index, 1); this.contacts[key]?.splice(index, 1);
this.checkChangeInput(); this.checkChangeInput();
}, },
copyLinkNetwork(value) { copyLinkNetwork(value) {
@@ -208,24 +222,20 @@ export default {
color: var(--btn-blue-color) color: var(--btn-blue-color)
&.new &.new
color: var(--icon-violet-color) color: var(--icon-violet-color)
.input-wrapper
width: 302px
.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, auto) grid-template-rows: repeat(1, 1fr)
@media(max-width: 1900px) @media(max-width: 1440px)
grid-template-columns: repeat(2, 1fr) grid-template-columns: repeat(2, 1fr)
grid-template-rows: repeat(2, auto) grid-template-rows: repeat(2, 1fr)
@media(max-width: 1320px)
grid-template-columns: repeat(1, 1fr)
grid-template-rows: repeat(3, auto)
.network-field .network-field
background-color: var(--bg-light-grey) background-color: var(--bg-light-grey)
.label-field .label-field
min-width: 110px
color: var(--font-grey-color) color: var(--font-grey-color)
.delete-contact .delete-icon :deep(path)
color: var(--font-grey-color) fill: var(--font-grey-color)
.on-left .on-left
margin-right: 10px margin-right: 10px
.q-btn :deep(.q-ripple) .q-btn :deep(.q-ripple)

View File

@@ -1,5 +1,6 @@
<template lang="pug"> <template lang="pug">
medical-form-wrapper( medical-form-wrapper(
id="documents",
title="Документы", title="Документы",
:is-check-change="isCheckChange", :is-check-change="isCheckChange",
:is-edit="isEdit", :is-edit="isEdit",
@@ -7,31 +8,32 @@
:open-edit="openEdit", :open-edit="openEdit",
:save="saveChange" :save="saveChange"
) )
q-form.form-wrap.gap-24.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-4(v-for="data in configData", :key="data.dataLabel") .data-section.flex.flex-col.gap-2(v-for="data in configData", :key="data.dataLabel")
.font-semibold.text-sm.whitespace-nowrap {{data.dataLabel}} .font-semibold.text-sm.whitespace-nowrap {{data.dataLabel}}
.flex.w-full.justify-between.items-center.gap-4( .flex.w-full.items-center.gap-4(
v-for="field in data.fields", v-for="field in data.fields",
:key="field.label" :key="field.label"
) )
.label-field.font-sm.text-sm.whitespace-nowrap {{`${field.label} :`}} .label-field.font-sm.text-sm.whitespace-nowrap {{`${field.label} :`}}
.photo-field.flex.gap-3.items-center(v-if="field.type === 'photo'") .flex.gap-3.items-center.h-10(v-if="field.type === 'photo'")
q-avatar( q-avatar(
rounded, rounded,
size="40px", size="40px",
v-if="docData[data.dataKey][field.key]?.photo" v-if="docData[data.dataKey]?.attachments?.photo"
) )
img.h-10.w-10.object-cover( img.h-10.w-10.object-cover(
:src="docData[data.dataKey][field.key]?.photo", :src="docData[data.dataKey]?.attachments?.photo",
:alt="docData[data.dataKey][field.label]" :alt="docData[data.dataKey][field.label]"
) )
q-badge.delete( q-badge.delete(
floating, floating,
v-if="isEdit", v-if="isEdit",
rounded, rounded,
@click="removeImage(data?.dataKey, field?.key)" @click="removeImage(data?.dataKey, field?.key)",
:style="{padding: 0}"
) )
q-icon(name="app:icon-cancel", size="8px") q-icon.cancel-icon(name="app:cancel-mini", size="16px")
.replace-photo.font-medium.text-base.cursor-pointer( .replace-photo.font-medium.text-base.cursor-pointer(
v-if="isEdit", v-if="isEdit",
@click="openModal(data.dataKey)", @click="openModal(data.dataKey)",
@@ -44,46 +46,30 @@
base-input-date( base-input-date(
v-if="field.type === 'date'", v-if="field.type === 'date'",
v-model="docData[data.dataKey][field.key]", v-model="docData[data.dataKey][field.key]",
:name="field.key", :name="data.dataKey + ':' + field.key",
@update:model-value="checkChangeDocData", @update:model-value="checkChangeDocData",
:readonly="!isEdit", :readonly="!isEdit",
:width="302", width="100%",
:mask="field?.inputMask", :mask="field?.inputMask",
:rule="[(val) => !personDataField.includes(field.key) ? checkPassportFields(val, field.rules) : field.rules(val)]", :rule="(val) => !personDataField.includes(data.dataKey + ':' + field.key) ? checkPassportFields(val, field.rules) : field.rules(val)",
:autogrow="field.key === 'issued_by_org'", size="M",
size="M"
) )
base-input( base-input(
v-if="field.type === 'text' && field.key !== 'issued_by_org'", v-if="field.type === 'text'",
v-model="docData[data.dataKey][field.key]", v-model="docData[data.dataKey][field.key]",
:name="field.key", :name="data.dataKey + ':' + field.key",
@update:model-value="checkChangeDocData", @update:model-value="checkChangeDocData",
:readonly="!isEdit", :readonly="!isEdit",
:type="field.type", :type="field.type",
:width="302", width="100%",
:mask="field?.inputMask", :mask="field?.inputMask",
:rule="[(val) => !personDataField.includes(field.key) ? checkPassportFields(val, field.rules) : field.rules(val)]", :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( .icon-copy.my-auto.text-lg.label-field.cursor-pointer(
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])"
) )
base-textarea(
v-if="field.type === 'text' && field.key === 'issued_by_org'",
v-model="docData[data.dataKey][field.key]",
:name="field.key",
@update:model-value="checkChangeDocData",
:readonly="!isEdit",
:type="field.type",
:width="302",
:mask="field?.inputMask",
:rule="[(val) => !personDataField.includes(field.key) ? checkPassportFields(val, field.rules) : field.rules(val)]",
autogrow,
size="auto",
height="57px"
padding="10px 16px"
)
</template> </template>
<script> <script>
@@ -101,7 +87,7 @@ import TheNotificationProvider from "@/components/Notifications/TheNotificationP
import { addNotification } from "@/components/Notifications/notificationContext"; import { addNotification } from "@/components/Notifications/notificationContext";
import BaseInput from "@/components/base/BaseInput.vue"; import BaseInput from "@/components/base/BaseInput.vue";
import BaseInputDate from "@/components/base/BaseInputDate.vue"; import BaseInputDate from "@/components/base/BaseInputDate.vue";
import BaseTextarea from "@/components/base/BaseTextarea.vue.vue"; import { mapActions } from "vuex";
export default { export default {
name: "DocumentsForm", name: "DocumentsForm",
components: { components: {
@@ -112,7 +98,6 @@ export default {
BaseUploadPhoto, BaseUploadPhoto,
TheNotificationProvider, TheNotificationProvider,
BaseInputDate, BaseInputDate,
BaseTextarea,
}, },
data() { data() {
return { return {
@@ -121,7 +106,6 @@ export default {
docData: null, docData: null,
isCheckChange: false, isCheckChange: false,
isLoadingData: true, isLoadingData: true,
approvedPassportData: false,
copiedFields: [ copiedFields: [
"series", "series",
"number", "number",
@@ -130,34 +114,47 @@ export default {
], ],
initializationData: { initializationData: {
passport: { passport: {
category: "passport",
id: null, id: null,
series: null, series: null,
number: null, number: null,
issued_by_org: null, issued_by: null,
issued_by_org_code: null, issued_by_org_code: null,
issued_by_date: null, issued_at: null,
photo: null, attachments: null,
}, },
insurance_number: { insurance_number: {
insurance_number: null, id: null,
photo_insurance_number: null, category: "insurance_number",
number: null,
attachments: null,
}, },
tax_identification_number: { tax_identification_number: {
tax_identification_number: null, id: null,
photo_tax_identification_number: null, category: "tax_identification_number",
number: null,
attachments: null,
}, },
}, },
personDataField: ["insurance_number", "tax_identification_number"], personDataField: [
"insurance_number:number",
"tax_identification_number:number",
],
showModal: false, showModal: false,
photoId: "", photoId: "",
}; };
}, },
computed: { computed: {
initialDocData() { initialDocData() {
return this.$store.state.medical?.documents; return this.$store.state.medical.documents;
}, },
passportFields() { passportFields() {
let excludedFields = ["photo", "id"]; let excludedFields = [
"attachments",
"id",
"category",
"issued_by_org_code",
];
return Object.keys(this.docData.passport).filter( return Object.keys(this.docData.passport).filter(
(key) => !excludedFields.includes(key) (key) => !excludedFields.includes(key)
); );
@@ -173,15 +170,13 @@ export default {
.getValidationComponents() .getValidationComponents()
.filter((elem) => !this.personDataField.includes(elem.name)) .filter((elem) => !this.personDataField.includes(elem.name))
.forEach((elem) => elem.resetValidation()); .forEach((elem) => elem.resetValidation());
this.approvedPassportData = true;
return true; return true;
} }
this.approvedPassportData = false;
return defaultRule(val); return defaultRule(val);
}, },
removeImage(docKey, field) { removeImage(docKey, field) {
this.docData[docKey][field] = {}; this.docData[docKey][field] = {};
this.isCheckChange = true; this.checkChangeDocData();
}, },
copyValue(text) { copyValue(text) {
navigator.clipboard.writeText(text); navigator.clipboard.writeText(text);
@@ -190,9 +185,7 @@ export default {
return !this.isEdit && this.copiedFields.some((elem) => elem === key); return !this.isEdit && this.copiedFields.some((elem) => elem === key);
}, },
cancelEdit() { cancelEdit() {
this.docData = JSON.parse(JSON.stringify(this.initialDocData)); this.docData = this.destruct(this.initialDocData);
this.docData.passport.issued_by_date =
this.initialDocData?.passport?.issued_by_date;
this.isEdit = false; this.isEdit = false;
this.isCheckChange = false; this.isCheckChange = false;
this.$refs.documentForm.resetValidation(); this.$refs.documentForm.resetValidation();
@@ -201,7 +194,10 @@ export default {
this.isEdit = true; this.isEdit = true;
}, },
checkChangeDocData() { checkChangeDocData() {
this.isCheckChange = checkChangeData(this.initialDocData, this.docData); this.isCheckChange = checkChangeData(
this.destruct(this.initialDocData),
this.docData
);
}, },
openModal(id) { openModal(id) {
this.photoId = id; this.photoId = id;
@@ -217,95 +213,48 @@ export default {
if (!validate) { if (!validate) {
getFieldsNameUnvalidated(form).forEach((elem) => { getFieldsNameUnvalidated(form).forEach((elem) => {
let error = {}; let error = {};
this.configData.forEach(({ fields }) => { this.configData.forEach(({ dataKey, fields }) => {
let findedElem = fields.find((el) => el.key === elem); let findedElem = fields.find(
(el) => dataKey + ":" + el.key === elem
);
if (findedElem) error = findedElem?.errorMessage; if (findedElem) error = findedElem?.errorMessage;
}); });
this.addErrorNotification(error?.title, error?.message); this.addErrorNotification(error?.title, error?.message);
}); });
} else { } else {
let passportFormData = new FormData();
let documentsFormData = new FormData();
form.getValidationComponents().forEach((elem) => {
if (!this.personDataField.includes(elem.name)) {
if (elem.name === "issued_by_date") {
passportFormData.append(
elem.name,
this.convertFormat(elem.modelValue) ?? ""
);
} else passportFormData.append(elem.name, elem.modelValue ?? "");
} else documentsFormData.append(elem.name, elem.modelValue ?? "");
});
this.checkChangePhoto("passport", "photo", passportFormData);
this.checkChangePhoto(
"insurance_number",
"photo_insurance_number",
documentsFormData
);
this.checkChangePhoto(
"tax_identification_number",
"photo_tax_identification_number",
documentsFormData
);
let request = []; let request = [];
if (!this.approvedPassportData) { Object.keys(this.docData).forEach((elem) => {
if (!this.initialDocData.passport?.id) { this.checkChangePhoto(elem, "attachments");
passportFormData.append("kind", "PASSPORT"); let newData = JSON.parse(JSON.stringify(this.docData[elem]));
passportFormData.append( newData.attachments = {};
"person", if (!checkChangeData(this.initialDocData?.[elem], newData)) return;
this.$store.state.medical?.basicData?.personalData?.id delete newData.id;
); if (newData?.issued_by_org_code) {
request.push( console.log(
this.sendData( "passport issued_by_org_code",
"general/identity_document/create/", newData.issued_by_org_code
passportFormData
)
); );
delete newData.issued_by_org_code;
}
delete newData.attachments;
Object.keys(this.docData[elem])?.forEach((key) => {
if (!this.docData[elem]?.[key]) delete newData?.[key];
});
if (!this.initialDocData?.[elem]?.id) {
newData.person_id =
this.$store.state.medical?.basicData?.personalData?.id;
request.push(this.createData("documents/", newData));
} else { } else {
if (this.checkPhotoDeletion("passport", "photo"))
request.push( request.push(
this.deletePhoto( this.updateData(
`general/identity_document/${this.initialDocData.passport?.id}/delete_photo/` `documents/${this.initialDocData?.[elem]?.id}`,
) newData
);
request.push(
this.sendData(
`general/identity_document/${this.initialDocData.passport?.id}/update/`,
passportFormData
) )
); );
} }
} });
if (
this.checkPhotoDeletion(
"insurance_number",
"photo_insurance_number"
)
)
request.push(
this.deletePhoto(
`general/person/${this.$store.state.medical?.basicData?.personalData?.id}/delete_photo_insurance_number/`
)
);
if (
this.checkPhotoDeletion(
"tax_identification_number",
"photo_tax_identification_number"
)
)
request.push(
this.deletePhoto(
`general/person/${this.$store.state.medical?.basicData?.personalData?.id}/delete_photo_tax_identification_number/`
)
);
request.push(
this.sendData(
`general/person/${this.$store.state.medical?.basicData?.personalData?.id}/update/`,
documentsFormData
)
);
Promise.allSettled(request).then(() => { Promise.allSettled(request).then(() => {
this.$store.dispatch("getMedicalCardData"); this.getMedicalDataById(this.$route.params.id);
this.isEdit = false; this.isEdit = false;
this.isCheckChange = false; this.isCheckChange = false;
this.$refs.form?.resetValidation(); this.$refs.form?.resetValidation();
@@ -313,18 +262,19 @@ export default {
} }
}); });
}, },
checkChangePhoto(updatedObjId, field, formData) { checkChangePhoto(updatedObjId, field) {
if (this.docData[updatedObjId][field]?.file) if (this.docData[updatedObjId][field]?.file)
formData.append(field, this.docData[updatedObjId][field]?.file[0]); console.log(
}, updatedObjId,
checkPhotoDeletion(updatedObjId, field) { field,
return ( this.docData[updatedObjId][field]?.file[0]
!Object.keys(this.docData[updatedObjId][field]).length &&
this.initialDocData[updatedObjId][field]?.photo
); );
}, },
async sendData(url, body) { async createData(url, body) {
return await fetchWrapper.post(url, body, "formData"); return await fetchWrapper.post(url, body);
},
async updateData(url, body) {
return await fetchWrapper.patch(url, body);
}, },
async deletePhoto(url) { async deletePhoto(url) {
return await fetchWrapper.del(url); return await fetchWrapper.del(url);
@@ -333,7 +283,19 @@ export default {
addNotification(title + message, title, message, "error", 5000); addNotification(title + message, title, message, "error", 5000);
}, },
convertFormat(date) { convertFormat(date) {
return date ? date.split(".").reverse().join("-") : null; return date ? date.split(".").reverse().join("-") : "";
},
...mapActions({
getMedicalDataById: "getMedicalDataById",
}),
destruct(data) {
const { passport, insurance_number, tax_identification_number } =
JSON.parse(JSON.stringify(data));
return {
passport: passport,
insurance_number: insurance_number,
tax_identification_number: tax_identification_number,
};
}, },
}, },
watch: { watch: {
@@ -341,11 +303,8 @@ export default {
deep: true, deep: true,
immediate: true, immediate: true,
handler(value) { handler(value) {
if (Object.keys(value).length) { if (Object.keys(value).length) this.docData = this.destruct(value);
this.docData = JSON.parse(JSON.stringify(this.initialDocData)); else this.docData = this.initializationData;
this.docData.passport.issued_by_date =
this.initialDocData?.passport?.issued_by_date;
} else this.docData = this.initializationData;
}, },
}, },
}, },
@@ -356,13 +315,11 @@ export default {
display: grid display: grid
grid-template-columns: repeat(3, 1fr) grid-template-columns: repeat(3, 1fr)
grid-template-rows: repeat(1, 1fr) grid-template-rows: repeat(1, 1fr)
@media(max-width: 1900px) @media(max-width: 1440px)
grid-template-columns: repeat(2, 1fr) grid-template-columns: repeat(2, 1fr)
grid-template-rows: 2.5fr 0.5fr grid-template-rows: 3fr 1fr
@media(max-width: 1320px)
grid-template-columns: repeat(1, 1fr)
grid-template-rows: 2.5fr 0.5fr 0.5fr
.label-field .label-field
min-width: 110px
color: var(--font-grey-color) color: var(--font-grey-color)
.replace-photo .replace-photo
color: var(--btn-blue-color) color: var(--btn-blue-color)
@@ -370,9 +327,13 @@ export default {
min-width: 302px min-width: 302px
.icon-copy .cursor .icon-copy .cursor
cursor: pointer !important cursor: pointer !important
.icon-cancel :deep(path)
fill: var(--default-white)
.q-badge .q-badge
padding: 4px padding: 4px
cursor: pointer cursor: pointer
.delete .delete
background-color: var(--btn-red-color) background-color: var(--btn-red-color)
.cancel-icon :deep(path)
fill: white
</style> </style>

View File

@@ -1,5 +1,6 @@
<template lang="pug"> <template lang="pug">
medical-form-wrapper( medical-form-wrapper(
id="insurance",
title="Страховка", title="Страховка",
:is-loading-data="isLoadingData", :is-loading-data="isLoadingData",
:is-check-change="isCheckChange", :is-check-change="isCheckChange",
@@ -8,32 +9,36 @@
:save="saveChange", :save="saveChange",
:cancel="cancelEdit" :cancel="cancelEdit"
) )
.form-wrap.gap-24.w-full q-form.form-wrap.gap-6.w-full(ref="insuranceForm", :no-error-focus="true")
.flex.flex-col.gap-4(v-for="insurance in insuranceConfig", :key="insurance.insuranceKey") .flex.flex-col.gap-2(v-for="insurance in insuranceConfig", :key="insurance.insuranceKey")
.font-semibold.text-sm.whitespace-nowrap {{insurance.insuranceLabel}} .font-semibold.text-sm.whitespace-nowrap {{insurance.insuranceLabel}}
.flex.w-full.justify-between.items-center.gap-4( .flex.w-full.items-center.gap-4(
v-for="field in insurance.fields", v-for="field in insurance.fields",
:key="insurance.insuranceKey + field.key", :key="insurance.insuranceKey + field.key",
: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} :`}}
.avatar-field.flex.gap-3.items-center.h-10.w-10(v-if="field.type === 'avatar'") .flex.gap-3.items-center.h-10.w-10(
.flex.w-10.h-10.relative(v-if="basic[insurance.insuranceKey][field.key]?.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")
img.rounded.avatar.object-cover( img.rounded.avatar.object-cover(
:src="basic[insurance.insuranceKey][field.key].photo", :src="insuranceData[insurance.insuranceKey]?.attachments?.photo",
alt="AV" alt="AV"
) )
q-badge.delete( q-badge.delete(
floating, floating,
v-if="isEdit", v-if="isEdit",
@click="removeImage(insurance?.insuranceKey, field?.key)", @click="removeImage(insurance?.insuranceKey, field?.key)",
rounded rounded,
:style="{padding: 0}"
) )
q-icon(name="app:icon-cancel", size="8px") q-icon.cancel-icon(name="app:cancel-mini", size="16px")
.replace-photo.font-medium.text-base.cursor-pointer( .replace-photo.font-medium.text-base.cursor-pointer(
v-if="isEdit", v-if="isEdit",
@click="changeModal(insurance.insuranceKey)" @click="changeModal(insurance.insuranceKey)"
) {{ basic[insurance.insuranceKey][field.key]?.photo ? "Заменить фото" : "Добавить фото" }} ) {{ insuranceData[insurance.insuranceKey]?.attachments?.photo ? "Заменить фото" : "Добавить фото" }}
base-modal( base-modal(
v-if="insurance.insuranceKey === photoId" v-if="insurance.insuranceKey === photoId"
v-model="showModal", v-model="showModal",
@@ -41,18 +46,18 @@
) )
base-upload-photo( base-upload-photo(
:key="insurance.insuranceKey" :key="insurance.insuranceKey"
v-model="basic[photoId][field.key]" v-model="insuranceData[photoId][field.key]"
:confirm-upload="confirmChangePhoto" :confirm-upload="confirmChangePhoto"
) )
.flex.h-10.relative( .flex.h-10.relative.gap-x-2.w-full(v-else-if="field.type === 'select'")
v-else-if="field.type === 'select'", .w-full.flex.items-center.change.px-4.rounded {{selectCategory(basic[insurance.insuranceKey][field.key])}}
@click="openModalCategories" q-btn.change.flex.w-7.h-9.rounded-md(
v-if="isEdit",
@click="openModalCategories",
dense,
padding="8px"
) )
q-field.items-center.categories( q-icon.icon(name="app:folder", size="24px")
:style="{cursor: isEdit ? 'pointer' : 'default'}",
:readonly="!isEdit",
outlined
) {{selectCategory(basic[insurance.insuranceKey][field.key])}}
base-modal( base-modal(
default-padding, default-padding,
hide-header, hide-header,
@@ -75,35 +80,39 @@
span( span(
:style="{color: 'var(--font-dark-blue-color)'}" :style="{color: 'var(--font-dark-blue-color)'}"
) {{"\xa0" + basic?.benefit[insurance?.discount] + "%"}} ) {{"\xa0" + basic?.benefit[insurance?.discount] + "%"}}
.input-container.flex.flex-col.relative(v-else) .w-full.flex.flex-col.relative(v-else)
base-input( base-input(
v-model="basic[insurance.insuranceKey][field.key]", v-model="insuranceData[insurance.insuranceKey][field.key]",
@update:model-value="checkChangeInput", @update:model-value="checkChangeInput",
:mask="field?.inputMask",
:readonly="!isEdit", :readonly="!isEdit",
:type="field.type", size="M",
size="M" width="100%",
:name="insurance.insuranceKey + ':' + field.key",
:rule="[(val) => checkFields(val, insurance.insuranceKey, field.rules)]"
) )
q-icon.my-auto.text-lg.label-field.cursor-pointer( q-icon.my-auto.text-lg.label-field.cursor-pointer.copy(
size="18px", size="18px",
name="app:icon-copy", name="app:copy",
v-if="checkCopiedFields(field.key, basic[insurance.insuranceKey])", v-if="checkCopiedFields(field.key, insuranceData[insurance.insuranceKey])",
@click="copyValue(basic[insurance.insuranceKey][field.key])" @click="copyValue(insuranceData[insurance.insuranceKey][field.key])"
) )
</template> </template>
<script> <script>
import { baseInsuranceForm } from "@/pages/newMedicalCard/utils/medicalConfig"; import { baseInsuranceForm } from "@/pages/newMedicalCard/utils/medicalConfig";
import { mapGetters, mapState } from "vuex"; import { mapActions, mapGetters, mapState } from "vuex";
import MedicalFormWrapper from "@/pages/newMedicalCard/components/MedicalFormWrapper.vue"; import MedicalFormWrapper from "@/pages/newMedicalCard/components/MedicalFormWrapper.vue";
import BaseAvatar from "@/components/base/BaseAvatar.vue"; import BaseAvatar from "@/components/base/BaseAvatar.vue";
import { fetchWrapper } from "@/shared/fetchWrapper"; import { fetchWrapper } from "@/shared/fetchWrapper";
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 BaseCategorySelection from "@/components/base/BaseCategorySelection.vue"; import BaseCategorySelection from "@/components/base/BaseCategorySelection.vue";
import { checkChangeData } from "@/shared/utils/changesObjects";
import { addNotification } from "@/components/Notifications/notificationContext"; import { addNotification } from "@/components/Notifications/notificationContext";
import BaseInput from "@/components/base/BaseInput.vue"; import BaseInput from "@/components/base/BaseInput.vue";
import {
checkChangeData,
getFieldsNameUnvalidated,
} from "@/shared/utils/changesObjects.js";
export default { export default {
name: "InsuranceForm", name: "InsuranceForm",
@@ -125,28 +134,70 @@ export default {
showModalCategories: false, showModalCategories: false,
photoId: "", photoId: "",
copiedFields: ["series", "number"], copiedFields: ["series", "number"],
insuranceData: {},
initializationData: {
OMS: {
category: "OMS",
id: null,
series: null,
number: null,
issued_by: null,
issued_at: "",
attachments: null,
},
DMS: {
category: "OMS",
id: null,
series: null,
number: null,
issued_by: null,
issued_at: "",
attachments: null,
},
},
}; };
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
url: "getUrl", url: "getUrl",
avatar: "getAvatar",
initDataBasic: "getBasicData",
}), }),
...mapState({ ...mapState({
basic: (state) => state.medical.basicData, basic: (state) => state.medical?.basicData,
benefitData: (state) => state.medical.benefitData, benefitData: (state) => state.medical?.benefitData,
}), }),
initialDocData() {
return this.$store.state.medical.documents;
},
}, },
methods: { methods: {
checkFields(val, insuranceKey, defaultRule) {
const filteredFields = ["series", "number"];
if (
filteredFields?.every(
(elem) => !this.insuranceData?.[insuranceKey]?.[elem]
) &&
!this.initialDocData?.[insuranceKey]?.id
) {
this.$refs.insuranceForm
.getValidationComponents()
.filter(
(elem) =>
elem.name === `${insuranceKey}:series` ||
elem.name === `${insuranceKey}:number`
)
.forEach((elem) => elem.resetValidation());
return true;
}
return defaultRule(val);
},
filterDataBenefit(text) { filterDataBenefit(text) {
if (text.length > 1) if (text.length > 1)
return this.$store.dispatch("getBenefitDataSearch", text); return this.$store.dispatch("getBenefitDataSearch", text);
this.$store.dispatch("getBenefitData", text); this.$store.dispatch("getBenefitData", text);
}, },
removeImage(docKey, field) { removeImage(docKey, field) {
this.basic[docKey][field] = {}; this.insuranceData[docKey][field] = {};
this.isCheckChange = true; this.checkChangeInput();
}, },
checkCopiedFields(key, item) { checkCopiedFields(key, item) {
return ( return (
@@ -170,6 +221,7 @@ export default {
this.isCheckChange = true; this.isCheckChange = true;
}, },
changeModal(id) { changeModal(id) {
if (id === "benefit") return;
this.photoId = id; this.photoId = id;
this.showModal = true; this.showModal = true;
}, },
@@ -177,191 +229,101 @@ export default {
this.showModalCategories = false; this.showModalCategories = false;
}, },
openModalCategories() { openModalCategories() {
if (this.isEdit) this.showModalCategories = true; //if (this.isEdit) this.showModalCategories = true;
}, },
checkChangeInput() { checkChangeInput() {
this.isCheckChange = this.isCheckChange = checkChangeData(
JSON.stringify(this.initDataBasic) !== JSON.stringify(this.basic) this.destruct(this.initialDocData),
? true this.insuranceData
: false; );
}, },
confirmChangePhoto() { confirmChangePhoto() {
this.showModal = false; this.showModal = false;
this.isCheckChange = true; this.isCheckChange = true;
}, },
saveChange() {
updateBasicData() { const form = this.$refs.insuranceForm;
let insuranceDMS = this.basic.insuranceDMS; form.validate().then((validate) => {
let insuranceOMS = this.basic.insuranceOMS; if (!validate) {
let personal = this.basic.personalData; getFieldsNameUnvalidated(form).forEach((elem) => {
let benefit = this.basic.benefit; let error = {};
this.insuranceConfig.forEach(({ insuranceKey, fields }) => {
const insuranceFormDataDMS = new FormData(); let findedElem = fields.find(
const insuranceFormDataOMS = new FormData(); (el) => insuranceKey + ":" + el.key === elem
const benefitFormData = new FormData();
if (insuranceDMS.series && insuranceDMS.number) {
for (let key in insuranceDMS) {
if (insuranceDMS[key] && key !== "photo" && key !== "id")
insuranceFormDataDMS.append(key, insuranceDMS[key]);
}
}
if (
insuranceDMS.photo.file &&
insuranceDMS.photo.photo !== this.initDataBasic.insuranceDMS.photo.photo
)
insuranceFormDataDMS.append("photo", insuranceDMS.photo.file[0]);
if (insuranceOMS.series && insuranceOMS.number) {
for (let key in insuranceOMS) {
if (insuranceOMS[key] && key !== "photo" && key !== "id")
insuranceFormDataOMS.append(key, insuranceOMS[key]);
}
}
if (
insuranceOMS.photo.file &&
insuranceOMS.photo.photo !== this.initDataBasic.insuranceOMS.photo.photo
)
insuranceFormDataOMS.append("photo", insuranceOMS.photo.file[0]);
const request = Object.keys(this.basic).map((key) => {
if (
key === "insuranceDMS" &&
!insuranceDMS?.id &&
insuranceDMS.series &&
insuranceDMS.number
) {
insuranceFormDataDMS.append("title", "DMS");
insuranceFormDataDMS.append("person", personal.id);
return this.sendData(
`general/insurance_policy/create/`,
insuranceFormDataDMS
); );
} if (findedElem) error = findedElem?.errorMessage;
if (
key === "insuranceOMS" &&
!insuranceOMS?.id &&
insuranceOMS.series &&
insuranceOMS.number
) {
insuranceFormDataOMS.append("title", "OMS");
insuranceFormDataOMS.append("person", personal.id);
return this.sendData(
`general/insurance_policy/create/`,
insuranceFormDataOMS
);
}
if (
checkChangeData(this.initDataBasic.insuranceDMS, insuranceDMS) &&
key === "insuranceDMS" &&
insuranceDMS.id
) {
this.checkedPhoto(
insuranceDMS.photo,
insuranceDMS.id,
"insurance_policy",
"delete_photo"
);
insuranceFormDataDMS.append("title", "DMS");
return this.sendData(
`general/insurance_policy/${insuranceDMS.id}/update/`,
insuranceFormDataDMS
);
} else if (
insuranceDMS.id &&
(!insuranceDMS.series || !insuranceDMS.number)
) {
this.addErrorNotification(
"Ошибка заполнения формы ДМС",
"Пожалуйста заполните все поля"
);
}
if (
checkChangeData(this.initDataBasic.insuranceOMS, insuranceOMS) &&
key === "insuranceOMS" &&
insuranceOMS.id
) {
this.checkedPhoto(
insuranceOMS.photo,
insuranceOMS.id,
"insurance_policy",
"delete_photo"
);
insuranceFormDataOMS.append("title", "OMS");
return this.sendData(
`general/insurance_policy/${insuranceOMS.id}/update/`,
insuranceFormDataOMS
);
} else if (
insuranceOMS.id &&
(!insuranceOMS.series || !insuranceOMS.number)
) {
this.addErrorNotification(
"Ошибка заполнения формы ОМС",
"Пожалуйста заполните все поля"
);
}
if (
checkChangeData(this.initDataBasic.benefit, benefit) &&
key === "benefit"
) {
this.checkedPhoto(
benefit.photo,
personal.id,
"person",
"delete_photo_benefit"
);
fetchWrapper.post(`general/person/${personal.id}/update/`, {
benefit: benefit.id,
}); });
if ( this.addErrorNotification(error?.title, error?.message);
benefit.photo.file && });
benefit.photo.photo !== this.initDataBasic.benefit.photo.photo } else {
) { let request = [];
benefitFormData.append("photo_benefit", benefit.photo.file[0]); Object.keys(this.insuranceData).forEach((elem) => {
return this.sendData( this.checkChangePhoto(elem, "attachments");
`general/person/${personal.id}/update/`, let newData = JSON.parse(JSON.stringify(this.insuranceData[elem]));
benefitFormData newData.attachments = {};
if (!checkChangeData(this.initialDocData?.[elem], newData)) return;
delete newData.id;
delete newData.attachments;
Object.keys(this.insuranceData[elem])?.forEach((key) => {
if (!this.insuranceData?.[elem]?.[key]) delete newData?.[key];
});
if (!this.initialDocData?.[elem]?.id) {
newData.person_id =
this.$store.state.medical?.basicData?.personalData?.id;
request.push(this.createData("documents/", newData));
} else
request.push(
this.updateData(
`documents/${this.initialDocData?.[elem]?.id}`,
newData
)
); );
} });
if (request.length)
Promise.allSettled(request).then(() => {
this.getMedicalDataById(this.$route.params.id);
this.isEdit = false;
this.isCheckChange = false;
this.$refs.form?.resetValidation();
});
} }
}); });
return request;
}, },
checkedPhoto(photo, id, key, title) { checkChangePhoto(updatedObjId, field) {
if (!photo?.photo) { if (this.insuranceData[updatedObjId][field]?.file)
return this.delPhoto(`general/${key}/${id}/${title}/`); console.log(
} updatedObjId,
field,
this.insuranceData[updatedObjId][field]?.file[0]
);
}, },
addErrorNotification(title, message) { addErrorNotification(title, message) {
addNotification(title + message, title, message, "error", 5000); addNotification(title + message, title, message, "error", 5000);
}, },
async createData(url, body) {
async sendData(url, body) { return await fetchWrapper.post(url, body);
return await fetchWrapper.post(url, body, "formData");
}, },
async delPhoto(url) { async updateData(url, body) {
return await fetchWrapper.del(url); return await fetchWrapper.patch(url, body);
}, },
cancelEdit() { cancelEdit() {
this.$store.dispatch("returnInitData"); this.insuranceData = this.destruct(this.initialDocData);
this.isEdit = false; this.isEdit = false;
this.isCheckChange = false; this.isCheckChange = false;
}, },
openEdit() { openEdit() {
this.isEdit = true; this.isEdit = true;
}, },
saveChange() { destruct(data) {
this.isLoadingData = true; const { OMS, DMS } = JSON.parse(JSON.stringify(data));
let updateBasic = this.updateBasicData(); return {
Promise.allSettled(updateBasic) OMS: OMS,
.then(() => this.$store.dispatch("getMedicalCardData")) DMS: DMS,
.then(() => { };
this.isLoadingData = false;
this.isEdit = false;
this.isCheckChange = false;
});
}, },
...mapActions({
getMedicalDataById: "getMedicalDataById",
}),
}, },
watch: { watch: {
showModalCategories: { showModalCategories: {
@@ -370,6 +332,15 @@ export default {
if (newVal) this.$store.dispatch("getBenefitData"); if (newVal) this.$store.dispatch("getBenefitData");
}, },
}, },
initialDocData: {
deep: true,
immediate: true,
handler(newVal) {
if (Object.keys(newVal).length) {
this.insuranceData = this.destruct(newVal);
} else this.insuranceData = this.initializationData;
},
},
}, },
}; };
</script> </script>
@@ -382,15 +353,9 @@ export default {
background: rgba(0, 0, 0, 0.05) background: rgba(0, 0, 0, 0.05)
color: var(--font-dark-blue-color) color: var(--font-dark-blue-color)
.avatar-field
min-width: 302px
.avatar .avatar
height: 100% height: 100%
.input-container
width: 302px
.replace-photo .replace-photo
color: var(--btn-blue-color) color: var(--btn-blue-color)
@@ -398,14 +363,12 @@ export default {
display: grid display: grid
grid-template-columns: repeat(3, 1fr) grid-template-columns: repeat(3, 1fr)
grid-template-rows: repeat(1, 1fr) grid-template-rows: repeat(1, 1fr)
@media(max-width: 1900px) @media(max-width: 1440px)
grid-template-columns: repeat(2, 1fr) grid-template-columns: repeat(2, 1fr)
grid-template-rows: repeat(2, 1fr) grid-template-rows: 1.2fr 0.8fr
@media(max-width: 1320px)
grid-template-columns: repeat(1, 1fr)
grid-template-rows: repeat(3, 1fr)
.label-field .label-field
min-width: 110px
color: var(--font-grey-color) color: var(--font-grey-color)
.q-badge .q-badge
@@ -415,27 +378,19 @@ export default {
.delete .delete
background-color: var(--btn-red-color) background-color: var(--btn-red-color)
.icon :deep(path)
fill: var(--font-grey-color)
.categories :deep(.q-field__control) .icon-cancel :deep(path)
height: 40px fill: var(--default-white)
color: var(--font-dark-blue-color)
padding: 0 16px
background: var(--bg-light-grey) !important
&:before
border: none !important
transition: none
&:after
border: none !important
transition: none
transform: none !important
.categories :deep(.q-field__native) .copy :deep(path)
font-weight: 500 fill: var(--font-grey-color)
font-size: 16px
line-height: normal .change
color: var(--font-dark-blue-color) background: var(--bg-light-grey)
padding: 8px 0 color: var(--font-grey-color)
background: var(--bg-light-grey) !important border: 1px solid var(--gray-secondary)
.q-field--outlined.q-field--readonly :deep(.q-field__control) .q-field--outlined.q-field--readonly :deep(.q-field__control)
background: var(--bg-light-grey) !important background: var(--bg-light-grey) !important
@@ -447,4 +402,7 @@ export default {
.q-field--outlined.q-field--readonly :deep(.q-field__native) .q-field--outlined.q-field--readonly :deep(.q-field__native)
cursor: default cursor: default
.cancel-icon :deep(path)
fill: white
</style> </style>

View File

@@ -22,8 +22,8 @@
@click="() => copyLinkNetwork(network.username)" @click="() => copyLinkNetwork(network.username)"
style="background-color: var(--bg-light-grey)" style="background-color: var(--bg-light-grey)"
) )
q-icon.icon(:name="mapNetworks[network.kind].icon" :class="{old: network.id, new: !network.id}") q-icon.icon(:name="mapNetworks[network.category].icon" :class="{old: network.id, new: !network.id}")
span.text-smm.font-medium {{ mapNetworks[network.kind].title || network.username }} span.text-smm.font-medium {{ mapNetworks[network.category].title || network.username }}
.flex.relative .flex.relative
q-btn( q-btn(
v-if="isEdit" v-if="isEdit"
@@ -129,7 +129,7 @@ export default {
data() { data() {
return { return {
newNetwork: { newNetwork: {
kind: mapNetworks["TELEGRAM"], category: mapNetworks["TELEGRAM"],
username: "", username: "",
}, },
isEdit: false, isEdit: false,
@@ -169,13 +169,13 @@ export default {
saveNetwork() { saveNetwork() {
if (this.newNetwork.username) { if (this.newNetwork.username) {
this.confidants[this.index].networks.push({ this.confidants[this.index].networks.push({
kind: this.newNetwork.kind.id, category: this.newNetwork.category.id,
username: this.newNetwork.username, username: this.newNetwork.username,
}); });
this.isOpenPopupAdding = false; this.isOpenPopupAdding = false;
this.isCheckChange = true; this.isCheckChange = true;
this.newNetwork = { this.newNetwork = {
kind: mapNetworks["TELEGRAM"], category: mapNetworks["TELEGRAM"],
username: "", username: "",
}; };
} else { } else {

View File

@@ -27,19 +27,19 @@
icon-left icon-left
) )
template(v-slot:iconLeft) template(v-slot:iconLeft)
q-icon(name="app:search", size="20px", style="color: var(--font-grey-color)") q-icon.icon-grey(name="app:search", size="20px" )
.button .button
q-btn( q-btn(
icon="filter_alt", :style="{width: '40px', height: '40px'}",
:style="{width: '40px', height: '40px', color: 'var(--font-grey-color)'}",
padding="0" padding="0"
) )
q-icon.icon-grey(name="app:filter", size="24px")
.button .button
q-btn( q-btn(
icon="app:sort-number", :style="{width: '40px', height: '40px'}",
:style="{width: '40px', height: '40px', color: 'var(--font-grey-color)'}",
padding="0" padding="0"
) )
q-icon.icon-grey(name="app:sort-number", size="24px")
.field.flex.flex-col.overflow-y-scroll.gap-y-1(v-if="data.results.diseases") .field.flex.flex-col.overflow-y-scroll.gap-y-1(v-if="data.results.diseases")
.letter.flex.items-center.font-bold.text-base Некоторые инфекционные и паразитарные болезни .letter.flex.items-center.font-bold.text-base Некоторые инфекционные и паразитарные болезни
.checkbox.flex.gap-x-2.items-center(v-for="disease in data.results.diseases") .checkbox.flex.gap-x-2.items-center(v-for="disease in data.results.diseases")
@@ -171,4 +171,7 @@ export default {
.search :deep(.q-field__prepend) .search :deep(.q-field__prepend)
padding-right: 6px padding-right: 6px
.icon-grey :deep(path)
fill: var(--font-grey-color)
</style> </style>

View File

@@ -42,19 +42,19 @@
size="M", size="M",
) )
template(v-slot:iconLeft) template(v-slot:iconLeft)
q-icon(name="app:search", size="20px", style="color: var(--font-grey-color)") q-icon.icon-grey(name="app:search", size="20px" )
.button .button
q-btn( q-btn(
icon="filter_alt", :style="{width: '40px', height: '40px'}",
:style="{width: '40px', height: '40px', color: 'var(--font-grey-color)'}",
padding="0" padding="0"
) )
q-icon.icon-grey(name="app:filter", size="24px")
.button .button
q-btn( q-btn(
icon="app:sort-number", :style="{width: '40px', height: '40px'}",
:style="{width: '40px', height: '40px', color: 'var(--font-grey-color)'}",
padding="0" padding="0"
) )
q-icon.icon-grey(name="app:sort-number", size="24px")
.field.flex.flex-col.overflow-y-scroll .field.flex.flex-col.overflow-y-scroll
.checkbox.flex.flex-col(v-for="complaint in textSorting(data.results.tags)") .checkbox.flex.flex-col(v-for="complaint in textSorting(data.results.tags)")
.letter.flex.items-center.font-bold.justify-center {{ complaint.sym }} .letter.flex.items-center.font-bold.justify-center {{ complaint.sym }}
@@ -71,7 +71,7 @@
width="40px", width="40px",
@click="showModal = true", @click="showModal = true",
) )
q-icon(name="app:plus", size="16px") q-icon.icon-white(name="app:plus", size="24px")
base-modal(v-model="showModal", title="Создание тега", modal-padding) base-modal(v-model="showModal", title="Создание тега", modal-padding)
tag-creation-form(:close-modal="closeModal", :create-tag="createTag") tag-creation-form(:close-modal="closeModal", :create-tag="createTag")
.flex.gap-x-20.w-full(v-if="change || fillInspection") .flex.gap-x-20.w-full(v-if="change || fillInspection")
@@ -365,4 +365,10 @@ export default {
&::placeholder &::placeholder
color: var(--font-grey-color) color: var(--font-grey-color)
opacity: 1 opacity: 1
.icon-grey :deep(path)
fill: var(--font-grey-color)
.icon-white :deep(path)
fill: var(--default-white)
</style> </style>

View File

@@ -25,19 +25,19 @@
size="M" size="M"
) )
template(v-slot:iconLeft) template(v-slot:iconLeft)
q-icon(name="app:search", size="20px", style="color: var(--font-grey-color)") q-icon.icon-grey(name="app:search", size="20px" )
.button .button
q-btn( q-btn(
icon="filter_alt", :style="{width: '40px', height: '40px'}",
:style="{width: '40px', height: '40px', color: 'var(--font-grey-color)'}",
padding="0" padding="0"
) )
q-icon.icon-grey(name="app:filter", size="24px")
.button .button
q-btn( q-btn(
icon="app:sort-number", :style="{width: '40px', height: '40px'}",
:style="{width: '40px', height: '40px', color: 'var(--font-grey-color)'}",
padding="0" padding="0"
) )
q-icon.icon-grey(name="app:sort-number", size="24px")
.field.flex.flex-col.overflow-y-scroll(v-if="data.results.tags") .field.flex.flex-col.overflow-y-scroll(v-if="data.results.tags")
.checkbox.flex.flex-col(v-for="complaint in textSorting(data.results.tags)") .checkbox.flex.flex-col(v-for="complaint in textSorting(data.results.tags)")
.letter.flex.items-center.font-bold.justify-center {{ complaint.sym }} .letter.flex.items-center.font-bold.justify-center {{ complaint.sym }}
@@ -54,12 +54,12 @@
width="40px", width="40px",
@click="showModal = true", @click="showModal = true",
) )
q-icon(name="app:plus", size="16px") q-icon.icon-white(name="app:plus", size="24px")
base-modal(v-model="showModal", title="Создание тега", modal-padding) base-modal(v-model="showModal", title="Создание тега", modal-padding)
tag-creation-form(:close-modal="closeModal", :create-tag="createTag") tag-creation-form(:close-modal="closeModal", :create-tag="createTag")
.flex(v-if="data.title === 'Осмотр полости рта' && (fillInspection || change)", :style="{color: 'var(--btn-blue-color)'}") .flex(v-if="data.title === 'Осмотр полости рта' && (fillInspection || change)", :style="{color: 'var(--btn-blue-color)'}")
q-btn(no-caps, flat, padding="2px 4px") q-btn(no-caps, flat, padding="2px 4px")
q-icon(left, name="app:plus", size="12px") q-icon.icon-blue(left, name="app:plus", size="24px")
span.font-medium Добавить описание зуба span.font-medium Добавить описание зуба
</template> </template>
@@ -175,6 +175,8 @@ export default {
cursor: pointer cursor: pointer
border-radius: 4px border-radius: 4px
background: var(--bg-light-grey) background: var(--bg-light-grey)
&:hover
background: var(--border-light-grey-color)
.field .field
margin-right: -10px margin-right: -10px
@@ -214,7 +216,7 @@ export default {
.q-btn :deep(.q-ripple) .q-btn :deep(.q-ripple)
display: none display: none
.on-left .on-left
margin-right: 8px margin-right: 4px
.input :deep(.q-field__native) .input :deep(.q-field__native)
font-weight: 500 font-weight: 500
@@ -228,4 +230,13 @@ export default {
.search :deep(.q-field__prepend) .search :deep(.q-field__prepend)
padding-right: 6px padding-right: 6px
.icon-grey :deep(path)
fill: var(--font-grey-color)
.icon-white :deep(path)
fill: var(--default-white)
.icon-blue :deep(path)
fill: var(--btn-blue-color)
</style> </style>

View File

@@ -18,16 +18,15 @@
anchor="bottom left", anchor="bottom left",
self="top left" self="top left"
) )
.tag.item.flex.py-2.px-4.items-center.cursor-pointer.w-full.gap-x-2(
@click="addCategory"
)
span Добавить категорию
.menu-wrapper.flex.flex-col.h-40.overflow-y-auto .menu-wrapper.flex.flex-col.h-40.overflow-y-auto
.options.flex(v-for="item in options") .options.flex(v-for="item in options")
.item.flex.py-2.px-4.items-center.cursor-pointer( .item.flex.py-2.px-4.items-center.cursor-pointer(
@click="selected(item.label)" @click="selected(item.label)"
) {{item.label}} ) {{item.label}}
.tag.item.flex.py-2.px-4.items-center.cursor-pointer.w-full.gap-x-2(
@click="addCategory"
)
q-icon(name="app:icon-plus", size="10px",)
span Добавить категорию
.flex.flex-col(class="gap-x-1.5") .flex.flex-col(class="gap-x-1.5")
base-input( base-input(
v-model="model.tag", v-model="model.tag",

View File

@@ -36,16 +36,16 @@
.toogle.flex.flex-toogle.h-10.p-1.rounded.absolute .toogle.flex.flex-toogle.h-10.p-1.rounded.absolute
q-btn.w-12.h-full.rounded( q-btn.w-12.h-full.rounded(
size="12px", size="12px",
:style="{'backgroundColor': !isShowSecondView ? 'var(--bg-aqua-blue)' : 'var(--bg-light-grey)'}", :style="{'backgroundColor': !isShowSecondView ? 'var(--btn-blue-color)' : 'var(--bg-light-grey)'}",
@click="()=>{ isShowSecondView=false }", @click="()=>{ isShowSecondView=false }",
) )
img(:src="teeth") q-icon.tooth(:class="{'tooth-active': !isShowSecondView}", name="app:tooth-outline", size="24px")
q-btn.w-12.h-full.rounded( q-btn.w-12.h-full.rounded(
size="12px", size="12px",
:style="{'backgroundColor': isShowSecondView ? 'var(--bg-aqua-blue)' : 'var(--bg-light-grey)'}", :style="{'backgroundColor': isShowSecondView ? 'var(--btn-blue-color)' : 'var(--bg-light-grey)'}",
@click="()=>{isShowSecondView=true }", @click="()=>{isShowSecondView=true }",
) )
img(:src="teeth_2") q-icon.tooth(:class="{'tooth-active': isShowSecondView}", name="app:tooth", size="22px")
</template> </template>
<script> <script>
@@ -119,4 +119,10 @@ export default {
background-color: var(--bg-light-grey) background-color: var(--bg-light-grey)
top: 16px top: 16px
right: 24px right: 24px
.tooth :deep(path)
fill: var(--font-grey-color)
.tooth-active :deep(path)
fill: var(--default-white)
</style> </style>

View File

@@ -17,17 +17,17 @@
) )
.flex.flex-col.gap-y-6px .flex.flex-col.gap-y-6px
span.color-grey.line-height.text-sm.font-semibold Врач span.color-grey.line-height.text-sm.font-semibold Врач
.flex.gap-x-2 .flex.gap-x-2.items-center
base-avatar(:size="40") base-avatar(:size="40")
img.object-cover.h-full( img.object-cover.h-full(
v-if="userData?.photo", v-if="userData?.avatar",
:src="userData?.photo", :src="url + userData?.avatar",
alt="avatar" alt="avatar"
) )
span.text-sm.color-blue(v-else) {{ avatar }} span.text-sm.text-dark(v-else) {{ avatar }}
.flex.flex-col .flex.flex-col
span.text-sm.color-blue.line-height.mb-2px {{ employeeName }} span.text-sm.text-dark.line-height.mb-2px.font-medium {{ employeeName }}
span.color-grey.line-height.text-sm {{ userData?.job_title ? userData?.job_title : "Терапевт" }} span.color-grey.line-height.text-xsx.font-medium {{ userData?.job_title ? userData?.job_title : "Терапевт" }}
.flex.gap-2.mt-8 .flex.gap-2.mt-8
base-button( base-button(
type="secondary", type="secondary",
@@ -47,7 +47,7 @@ import BaseAvatar from "@/components/base/BaseAvatar.vue";
import BaseInputDate from "@/components/base/BaseInputDate.vue"; import BaseInputDate from "@/components/base/BaseInputDate.vue";
import BaseInputTime from "@/components/base/BaseInputTime.vue"; import BaseInputTime from "@/components/base/BaseInputTime.vue";
import * as moment from "moment/moment"; import * as moment from "moment/moment";
import { mapGetters, mapActions } from "vuex"; import { mapGetters, mapActions, mapState } from "vuex";
import BaseButton from "@/components/base/BaseButton.vue"; import BaseButton from "@/components/base/BaseButton.vue";
export default { export default {
name: "MedicalProtocolCreateModal", name: "MedicalProtocolCreateModal",
@@ -64,12 +64,15 @@ export default {
data() { data() {
return { return {
protocolData: { protocolData: {
date: null, date: "",
startTime: null, startTime: null,
}, },
}; };
}, },
computed: { computed: {
...mapState({
url: (state) => state.imgUrl,
}),
...mapGetters({ ...mapGetters({
userData: "getUserData", userData: "getUserData",
}), }),
@@ -93,19 +96,27 @@ export default {
...mapActions({ ...mapActions({
createProtocol: "createProtocol", createProtocol: "createProtocol",
}), }),
checkFormat(date) {
return date && date?.length === 10 ? date : null;
},
saveProtocol() { saveProtocol() {
if ( if (
!Object.keys(this.protocolData).every((key) => this.protocolData[key]) !Object.keys(this.protocolData).every((key) => this.protocolData[key])
) { )
return; return;
}
let time = this.protocolData.startTime.split(":"); let time = this.protocolData.startTime.split(":");
let start = moment(this.protocolData.date).hour(time[0]).minute(time[1]); let start = moment(this.checkFormat(this.protocolData.date))
?.hour(time[0])
?.minute(time[1]);
let createdProtocol = { let createdProtocol = {
start: start.isValid() ? start.format() : null, start: start.isValid() ? start.format() : null,
end: start.isValid() ? start.add(30, "m").format() : null, end: start.isValid() ? start.add(30, "m").format() : null,
status: null,
}; };
if (
Object.keys(createdProtocol)?.some((elem) => !createdProtocol?.[elem])
)
return;
createdProtocol.status = null;
this.createProtocol(createdProtocol); this.createProtocol(createdProtocol);
this.changeShownCreateModal(false); this.changeShownCreateModal(false);
this.createInspection(); this.createInspection();

View File

@@ -9,14 +9,14 @@
.flex.font-bold.text-6xl {{elem.label}} .flex.font-bold.text-6xl {{elem.label}}
.flex.gap-x-4 .flex.gap-x-4
.button .button
q-btn( q-btn.btn(
icon="print", icon="app:printer",
size="20px", size="20px",
:style="{width: '40px', height: '40px', color: 'var(--font-grey-color)'}", :style="{width: '40px', height: '40px', color: 'var(--font-grey-color)'}",
padding="0" padding="0"
) )
.button .button
q-btn( q-btn.btn(
icon="app:basket", icon="app:basket",
size="20px", size="20px",
:style="{width: '40px', height: '40px', color: 'var(--font-grey-color)'}", :style="{width: '40px', height: '40px', color: 'var(--font-grey-color)'}",
@@ -204,4 +204,7 @@ export default {
cursor: pointer cursor: pointer
border-radius: 4px border-radius: 4px
background: var(--bg-light-grey) background: var(--bg-light-grey)
.btn :deep(path)
fill: var(--font-grey-color)
</style> </style>

View File

@@ -7,7 +7,7 @@
@click="changeShownCreateModal(true)", @click="changeShownCreateModal(true)",
v-if="createInspection" v-if="createInspection"
) )
q-icon(name="app:plus", size="18px") q-icon.plus-icon(name="app:big-plus", size="24px")
.p-4.rounded.background.list.flex.flex-col.gap-y-6 .p-4.rounded.background.list.flex.flex-col.gap-y-6
.flex .flex
base-select( base-select(
@@ -157,4 +157,6 @@ export default {
color: var(--default-white) color: var(--default-white)
.color-grey .color-grey
color: var(--font-grey-color) color: var(--font-grey-color)
.plus-icon :deep(path)
fill: white
</style> </style>

View File

@@ -1,6 +1,6 @@
<template lang="pug"> <template lang="pug">
.base-info-wrapper.w-full.flex.gap-x-2.flex-1 .base-info-wrapper.w-full.flex.gap-x-2.flex-1.font-medium.text-m
medical-sidebar(v-model="currentMenuItem") //- medical-sidebar(v-model="currentMenuItem")
component( component(
v-if="currentMenuItem", v-if="currentMenuItem",
v-bind="{...componentProps, updateCurrentItem:updateCurrentItem, clearProps:clearProps}" v-bind="{...componentProps, updateCurrentItem:updateCurrentItem, clearProps:clearProps}"
@@ -49,4 +49,5 @@ export default {
.base-info-wrapper .base-info-wrapper
height: 80.4% height: 80.4%
max-height: calc(100% - 190px - 8px) max-height: calc(100% - 190px - 8px)
color: var(--font-dark-blue-color)
</style> </style>

View File

@@ -0,0 +1,206 @@
<template lang="pug">
.patient-info.pt-4.pr-6.flex.flex-col.justify-between.rounded.font-medium.text-m
.ml-6.flex.justify-between
//- TODO по клику переход на предыдущую страницу
q-breadcrumbs
template(v-slot:separator)
q-icon.rotate(
size="16px",
name="app:long-arrow",
color="grey",
)
q-breadcrumbs-el(:label="routes[routingHistory.state.back]")
q-breadcrumbs-el(:label="`Медкарта #${medicalCardData?.number}`")
q-btn(
@click="toggleEditMode",
: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)'}")
img(v-if="patientData?.photo", :src="url + patientData?.photo")
span.text-2xl(v-else) {{patientAvatar}}
template(v-if="!editMode")
.flex.gap-y-1.flex-col
.flex.items-center
span.text-xxl.font-bold.mr-3(
:style="{color: 'var(--font-dark-blue-color)', 'line-height': '135%'}",
) {{patientName}}
span.text-smm.label-color Добавлен в систему:
span.date-color {{ patientData?.gender }}
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}}
</template>
<script>
import BaseInput from "@/components/base/BaseInput.vue";
import { v_model } from "@/shared/mixins/v-model";
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 {
name: "MedicalCardPatientForm",
mixins: [v_model],
data() {
return {
menuItem: headerMenuItem,
routes: routesDictionary,
editMode: true,
fullname: "",
};
},
components: {
BaseInput,
},
computed: {
routingHistory() {
return this.$store.state.routingHistory;
},
url() {
return this.$store.state.url;
},
medicalCardData() {
return this.$store.state.medical.medicalCard;
},
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() {
let checkedFirstName =
this.patientData?.first_name !== null
? this.patientData?.first_name[0]
: this.patientData?.last_name[1];
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: {
selectItem(item) {
this.value = item?.component;
},
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;
},
},
};
</script>
<style scoped lang="sass">
.patient-info
background-color: var(--default-white)
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
font-size: 12px
line-height: 135%
color: var(--font-grey-color)
.q-breadcrumbs :deep(.q-breadcrumbs__separator)
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>

View File

@@ -10,7 +10,7 @@
padding="0", padding="0",
@click="addNewConfidant" @click="addNewConfidant"
) )
q-icon(name="app:icon-plus", size="12px", left) q-icon.icon(name="app:plus", size="24px", left)
span Добавить контакт span Добавить контакт
</template> </template>
@@ -46,7 +46,9 @@ export default {
&::-webkit-scrollbar &::-webkit-scrollbar
width: 0 width: 0
.on-left .on-left
margin-right: 10px margin-right: 4px
.q-btn :deep(.q-ripple) .q-btn :deep(.q-ripple)
display: none display: none
.icon :deep(path)
fill: var(--btn-blue-color)
</style> </style>

View File

@@ -21,7 +21,7 @@
name="app:edit", name="app:edit",
v-if="!isEdit && !noEditBtn", v-if="!isEdit && !noEditBtn",
@click="openEdit", @click="openEdit",
size="20" size="20px",
) )
.flex.w-full.gap-11px.items-center.justify-between(v-else) .flex.w-full.gap-11px.items-center.justify-between(v-else)
span.font-bold.text-base.whitespace-nowrap {{title}} span.font-bold.text-base.whitespace-nowrap {{title}}
@@ -30,13 +30,13 @@
:style="{color: 'var(--btn-blue-color)'}" :style="{color: 'var(--btn-blue-color)'}"
) )
base-button(type="secondary", width="142px") base-button(type="secondary", width="142px")
img.mr-2(:src="layers") q-icon.sample.mr-2(name="app:sample", size="20px")
span.text-smm.font-medium Шаблоны span.text-smm.font-medium Шаблоны
.flex.w-fit.gap-10px.items-center.delete-button.cursor-pointer( .flex.w-fit.gap-10px.items-center.delete-button.cursor-pointer(
v-if="!isEdit && titleDelete && deleteItem", v-if="!isEdit && titleDelete && deleteItem",
@click="deleteItem" @click="deleteItem"
) )
q-icon(name="app:basket" size="20") q-icon(name="app:basket" size="20px")
span.text-smm.font-medium {{ titleDelete }} span.text-smm.font-medium {{ titleDelete }}
slot slot
.flex.h-fit.w-fit.gap-2(v-if="isEdit && buttonsPresence") .flex.h-fit.w-fit.gap-2(v-if="isEdit && buttonsPresence")
@@ -113,6 +113,8 @@ export default {
.form-wrapper .form-wrapper
background-color: var(--default-white) background-color: var(--default-white)
.edit-button .edit-button
& > :deep(svg > path)
stroke: var(--btn-blue-color)
color: var(--btn-blue-color) color: var(--btn-blue-color)
&:hover &:hover
color: var(--btn-blue-color-hover) color: var(--btn-blue-color-hover)
@@ -134,4 +136,6 @@ export default {
transition: 0.3s ease transition: 0.3s ease
.medical-form-move .medical-form-move
transition: 0.3s ease transition: 0.3s ease
.sample :deep(path)
fill: var(--btn-blue-color)
</style> </style>

View File

@@ -1,5 +1,5 @@
<template lang="pug"> <template lang="pug">
.patient-info.pt-4.pr-6.flex.flex-col.justify-between.rounded .patient-info.pt-4.pr-6.flex.flex-col.justify-between.rounded.font-medium.text-m
.ml-6.flex.justify-between .ml-6.flex.justify-between
q-breadcrumbs q-breadcrumbs
template(v-slot:separator) template(v-slot:separator)
@@ -9,7 +9,7 @@
color="grey", color="grey",
) )
q-breadcrumbs-el(:label="routes[routingHistory.state.back]") q-breadcrumbs-el(:label="routes[routingHistory.state.back]")
q-breadcrumbs-el(:label="`${routes[routingHistory.state.current]} #${medicalCardData?.number}`") q-breadcrumbs-el(:label="`Медкарта #${medicalCardData?.number}`")
q-btn( q-btn(
flat, flat,
text-color="grey", text-color="grey",
@@ -30,6 +30,7 @@
:style="{color: 'var(--font-dark-blue-color)', 'line-height': '135%'}", :style="{color: 'var(--font-dark-blue-color)', 'line-height': '135%'}",
) {{patientName}} ) {{patientName}}
q-chip( q-chip(
v-if="priority?.text",
square, square,
text-color="white", text-color="white",
:style="{height: '20px', background: priority?.color, padding: '2px 8px'}", :style="{height: '20px', background: priority?.color, padding: '2px 8px'}",
@@ -166,10 +167,9 @@ export default {
</script> </script>
<style scoped lang="sass"> <style scoped lang="sass">
.patient-info .patient-info
width: 83.2%
height: 18.7%
background-color: var(--default-white) background-color: var(--default-white)
min-width: 1050px
min-height: 190px min-height: 190px
.menu-item .menu-item
color: var(--font-grey-color) color: var(--font-grey-color)

View File

@@ -1,10 +1,11 @@
<template lang="pug"> <template lang="pug">
.sidebar-wrapper.h-full.rounded.px-2.py-6.flex.flex-col .sidebar-wrapper.h-full.rounded.px-2.py-6.flex.flex-col.gap-1
.menu-item.text-base.px-4.py-3.rounded.cursor-pointer.whitespace-nowrap( .menu-item.text-base.px-4.py-3.rounded.cursor-pointer.whitespace-nowrap(
v-for="item in menuItem", v-for="item in menuItem",
:key="item.id", :key="item.id",
:id="item.id", :id="item.id",
@click="selectItem(item)", @click="selectItem(item)",
:title="item.hint || item.title"
:class="{'menu-item-active': item.component === modelValue}" :class="{'menu-item-active': item.component === modelValue}"
) {{item.title}} ) {{item.title}}
</template> </template>
@@ -32,7 +33,7 @@ export default {
</script> </script>
<style lang="sass" scoped> <style lang="sass" scoped>
.sidebar-wrapper .sidebar-wrapper
min-width: 300px min-width: 200px
min-height: 350px min-height: 350px
background-color: var(--default-white) background-color: var(--default-white)
.menu-item .menu-item

View File

@@ -49,6 +49,7 @@ export const baseDataForm = [
key: "photo", key: "photo",
label: "Фото", label: "Фото",
type: "avatar", type: "avatar",
disabled: true,
}, },
], ],
}, },
@@ -60,22 +61,28 @@ export const baseDataForm = [
key: "region", key: "region",
label: "Регион", label: "Регион",
type: "text", type: "text",
rules: [(val) => ruleNotValue(val)],
error: "Поле 'Регион' должны быть заполнены",
}, },
{ {
key: "city", key: "city",
label: "Город", label: "Город",
type: "text", type: "text",
rules: [(val) => ruleNotValue(val)],
error: "Поле 'Город' должны быть заполнены",
}, },
{ {
key: "street", key: "street",
label: "Улица", label: "Улица",
type: "text", type: "text",
rules: [(val) => ruleNotValue(val)],
error: "Поле 'Улица' должны быть заполнены",
}, },
{ {
key: "house", key: "house",
label: "Дом", label: "Дом",
type: "text", type: "text",
rules: [(val) => !/^[\W0]/.test(val)], rules: [(val) => ruleNotValue(val)],
error: error:
"Номер дома должен начинатся не с нуля и только с цифробуквенного символа", "Номер дома должен начинатся не с нуля и только с цифробуквенного символа",
}, },
@@ -145,32 +152,32 @@ export const mapNetworks = {
VK: { VK: {
id: "VK", id: "VK",
title: "VK", title: "VK",
icon: "app:icon-vk", icon: "app:vk",
network: "VK", network: "VK",
}, },
TELEGRAM: { TELEGRAM: {
id: "TELEGRAM", id: "TELEGRAM",
title: "Telegram", title: "Telegram",
icon: "app:icon-tg", icon: "app:tg",
network: "TELEGRAM", network: "TELEGRAM",
}, },
GMAIL: { GMAIL: {
id: "GMAIL", id: "GMAIL",
title: "Gmail", title: "Gmail",
icon: "app:icon-google", icon: "app:google",
network: "GMAIL", network: "GMAIL",
}, },
SLACK: { SLACK: {
id: "SLACK", id: "SLACK",
title: "Slack", title: "Slack",
icon: "app:icon-slack", icon: "app:slack",
network: "SLACK", network: "SLACK",
markerLink: "slack", markerLink: "slack",
}, },
DISCORD: { DISCORD: {
id: "DISCORD", id: "DISCORD",
title: "Discord", title: "Discord",
icon: "app:icon-discord", icon: "app:discord",
network: "DISCORD", network: "DISCORD",
}, },
VIBER: { VIBER: {
@@ -193,7 +200,7 @@ export const contactsDataForm = {
fieldName: "Телефон", fieldName: "Телефон",
error: error:
"Не корректно введен номер телефона. Номер телефона должен состоять из 10 цифр (без учета +7)", "Не корректно введен номер телефона. Номер телефона должен состоять из 10 цифр (без учета +7)",
kind: "PHONE", category: "PHONE",
rules: [(val) => ruleLengthValue(val, 18)], rules: [(val) => ruleLengthValue(val, 18)],
inputMask: "+7 (###) ###-##-##", inputMask: "+7 (###) ###-##-##",
}, },
@@ -203,14 +210,14 @@ export const contactsDataForm = {
error: error:
"Не корректно введен адрес почты. Почта должна содержать локальное имя, знак @ и имя домена", "Не корректно введен адрес почты. Почта должна содержать локальное имя, знак @ и имя домена",
rules: [(val) => ruleEmailValue(val)], rules: [(val) => ruleEmailValue(val)],
kind: "EMAIL", category: "EMAIL",
}, },
networks: { networks: {
title: "Социальные сети", title: "Социальные сети",
fieldName: "Соцсеть", fieldName: "Соцсеть",
error: "Отсутсвует ссылка соцсети", error: "Отсутсвует ссылка соцсети",
rules: [(val) => ruleNotValue(val)], rules: [(val) => ruleNotValue(val)],
kind: "TELEGRAM", category: "TELEGRAM",
}, },
}; };
@@ -338,7 +345,8 @@ export const baseInfoMenu = [
component: "MedicalBasicDataWrapper", component: "MedicalBasicDataWrapper",
}, },
{ {
title: ротоколы первичного осмотра", title: ПО",
hint: "Протоколы первичного осмотра",
id: "protocols", id: "protocols",
component: "MedicalProtocolsWrapper", component: "MedicalProtocolsWrapper",
}, },
@@ -372,47 +380,63 @@ export const baseInfoMenu = [
export const baseInsuranceForm = [ export const baseInsuranceForm = [
{ {
insuranceLabel: "ОМС", insuranceLabel: "ОМС",
insuranceKey: "insuranceOMS", insuranceKey: "OMS",
fields: [ fields: [
{ {
key: "series", key: "series",
label: "Серия", label: "Серия",
type: "text", type: "text",
inputMask: "####", rules: (val) => ruleNotValue(val),
errorMessage: {
title: "Ошибка валидации",
message: "Серия полиса ОМС не заполнена",
},
}, },
{ {
key: "number", key: "number",
label: "Номер", label: "Номер",
type: "text", type: "text",
inputMask: "####-####-####", rules: (val) => ruleNotValue(val),
errorMessage: {
title: "Ошибка валидации",
message: "Номер полиса ОМС не заполнен",
},
}, },
{ {
key: "photo", key: "attachments",
label: "Фото ОМС", label: "Фото ОМС",
type: "avatar", type: "photo",
}, },
], ],
}, },
{ {
insuranceLabel: "ДМС", insuranceLabel: "ДМС",
insuranceKey: "insuranceDMS", insuranceKey: "DMS",
fields: [ fields: [
{ {
key: "series", key: "series",
label: "Серия", label: "Серия",
type: "text", type: "text",
inputMask: "####", rules: (val) => ruleNotValue(val),
errorMessage: {
title: "Ошибка валидации",
message: "Серия полиса ДМС не заполнена",
},
}, },
{ {
key: "number", key: "number",
label: "Номер", label: "Номер",
type: "text", type: "text",
inputMask: "####-####-####", rules: (val) => ruleNotValue(val),
errorMessage: {
title: "Ошибка валидации",
message: "Номер полиса ДМС не заполнен",
},
}, },
{ {
key: "photo", key: "attachments",
label: "Фото ДМС", label: "Фото ДМС",
type: "avatar", type: "photo",
}, },
], ],
}, },
@@ -429,7 +453,7 @@ export const baseInsuranceForm = [
{ {
key: "photo", key: "photo",
label: "Документы", label: "Документы",
type: "avatar", type: "photo",
}, },
], ],
}, },
@@ -468,7 +492,7 @@ export const documentForm = [
rules: (val) => ruleLengthValue(val, 6), rules: (val) => ruleLengthValue(val, 6),
}, },
{ {
key: "issued_by_org", key: "issued_by",
label: "Кем выдан", label: "Кем выдан",
type: "text", type: "text",
errorMessage: { errorMessage: {
@@ -489,7 +513,7 @@ export const documentForm = [
rules: (val) => ruleLengthValue(val, 7), rules: (val) => ruleLengthValue(val, 7),
}, },
{ {
key: "issued_by_date", key: "issued_at",
label: "Дата выдачи", label: "Дата выдачи",
type: "date", type: "date",
rules: (val) => ruleDateValue(val), rules: (val) => ruleDateValue(val),
@@ -499,7 +523,7 @@ export const documentForm = [
}, },
}, },
{ {
key: "photo", key: "attachments",
label: "Фото паспорта", label: "Фото паспорта",
type: "photo", type: "photo",
}, },
@@ -510,7 +534,7 @@ export const documentForm = [
dataKey: "insurance_number", dataKey: "insurance_number",
fields: [ fields: [
{ {
key: "insurance_number", key: "number",
label: "Номер СНИЛС", label: "Номер СНИЛС",
type: "text", type: "text",
inputMask: "###-###-### ##", inputMask: "###-###-### ##",
@@ -518,10 +542,10 @@ export const documentForm = [
title: "Ошибка валидации", title: "Ошибка валидации",
message: "СНИЛС содержит менее 11 цифр", message: "СНИЛС содержит менее 11 цифр",
}, },
rules: (val) => ruleOptionalFields(val, 14), rules: (val, id) => ruleOptionalFields(val, 14, id),
}, },
{ {
key: "photo_insurance_number", key: "attachments",
label: "Фото СНИЛС", label: "Фото СНИЛС",
type: "photo", type: "photo",
}, },
@@ -532,7 +556,7 @@ export const documentForm = [
dataKey: "tax_identification_number", dataKey: "tax_identification_number",
fields: [ fields: [
{ {
key: "tax_identification_number", key: "number",
label: "Номер ИНН", label: "Номер ИНН",
type: "text", type: "text",
inputMask: "############", inputMask: "############",
@@ -540,10 +564,10 @@ export const documentForm = [
title: "Ошибка валидации", title: "Ошибка валидации",
message: "ИНН содержит менее 12 цифр", message: "ИНН содержит менее 12 цифр",
}, },
rules: (val) => ruleOptionalFields(val, 12), rules: (val, id) => ruleOptionalFields(val, 12, id),
}, },
{ {
key: "photo_tax_identification_number", key: "attachments",
label: "Фото ИНН", label: "Фото ИНН",
type: "photo", type: "photo",
}, },

View File

@@ -1,189 +0,0 @@
<template lang="pug">
.calendar-container.flex
calendar-sidebar(
:url="url",
:schedules-data="schedulesData",
:open-form-create="openFormCreateEvent",
:event-statuses="eventStatuses",
@width="changeWidth",
)
calendar-schedule(
:url="url",
:schedules-data="schedulesData",
:current-date="currentDate",
:time-information="timeInformation",
:events-data="eventsData",
:sidebar-width="sidebarWidth",
:event-statuses="eventStatuses",
@previous-date="switchPreviousDate",
@next-date="switchNextDate",
@selected-layout="changeCalendarLayout",
@selected-event="writeEventData",
@delete-event="openModal",
:open-form-create-event="openFormCreateEvent"
)
calendar-form-add-event(
v-model="isOpenForm",
:selected-event-data="selectedEvent",
:event-statuses="eventStatuses",
:time-information="timeInformation",
:current-date="currentDate",
@clear-event-data="clearSelectedEvent"
)
base-modal(
v-model="showModal",
title="Удаление события"
)
calendar-delete-modal(
:event-statuses="eventStatuses",
:owner-event="selectedEvent",
:close-modal="changeShowModal",
@update-events="fetchEventsData"
)
</template>
<script>
//TODO: Вынести emits в массив
import { fetchWrapper } from "../../shared/fetchWrapper.js";
import * as moment from "moment/moment";
import CalendarSchedule from "./components/CalendarSchedule.vue";
import CalendarSidebar from "./components/CalendarSidebar.vue";
import CalendarFormAddEvent from "./components/CalendarFormAddEvent.vue";
import BaseModal from "@/components/base/BaseModal.vue";
import CalendarDeleteModal from "./components/CalendarDeleteModal.vue";
import { statusesConfig } from "@/pages/oldCalendar/utils/statusesConfig";
export default {
name: "TheCalendar",
components: {
CalendarSchedule,
CalendarSidebar,
CalendarFormAddEvent,
BaseModal,
CalendarDeleteModal,
},
props: {
url: String,
},
data() {
return {
sidebarWidth: "72px",
calendarLayout: "",
currentDate: moment(),
selectedEvent: {},
showModal: false,
timeInformation: {
dayStartTime: "08:00",
dayEndTime: "22:00",
},
eventsData: [],
schedulesData: [],
isOpenForm: false,
//changeFormWasClosed: false,
eventStatuses: statusesConfig,
};
},
methods: {
switchPreviousDate() {
this.currentDate = this.currentDate.clone().subtract(1, "day");
},
switchNextDate() {
this.currentDate = this.currentDate.clone().add(1, "day");
},
changeCalendarLayout(option) {
this.calendarLayout = option;
},
saveEventsData(res) {
this.eventsData = res.results;
},
saveSchedulesData(res) {
if (res.results.length === 0) {
this.schedulesData = [];
return;
}
let filteredEmployees = res.results.filter(
(elem) => elem.status !== "DAY_OFF" && elem.status !== "VACATION"
);
if (filteredEmployees.length === 0) {
this.schedulesData = [];
return;
}
this.schedulesData = filteredEmployees;
},
fetchSchedulesData() {
fetchWrapper
.get(
`accounts/schedules/?date_after=${this.currentDate.format(
"YYYY-MM-DD"
)}&date_before=${this.currentDate.format("YYYY-MM-DD")}`
)
.then((res) => this.saveSchedulesData(res));
},
fetchEventsData() {
fetchWrapper
.get(
`registry/event/?limit=100&start=${this.currentDate.format(
"YYYY-MM-DD"
)}T${
this.timeInformation.dayStartTime
}:00Z&end=${this.currentDate.format("YYYY-MM-DD")}T${
this.timeInformation.dayEndTime
}:00Z`
)
.then((res) => this.saveEventsData(res));
},
changeWidth(value) {
this.sidebarWidth = value;
},
openFormCreateEvent() {
this.isOpenForm = true;
},
closeFormCreateEvent() {
this.isOpenForm = false;
this.fetchEventsData();
},
/*setChangeFormState() {
this.changeFormWasClosed = true;
},
resetChangeFormState() {
this.changeFormWasClosed = false;
},*/
writeEventData(eventData) {
this.selectedEvent = eventData;
this.openFormCreateEvent();
},
clearSelectedEvent() {
this.selectedEvent = {};
this.closeFormCreateEvent();
},
openModal(eventData) {
this.selectedEvent = eventData;
this.showModal = true;
},
changeShowModal() {
this.showModal = false;
},
},
watch: {
showModal: function () {
if (this.showModal === false) {
//this.setChangeFormState();
this.clearSelectedEvent();
}
},
currentDate() {
this.fetchSchedulesData();
this.fetchEventsData();
},
},
mounted() {
this.fetchSchedulesData();
this.fetchEventsData();
},
};
</script>
<style lang="sass" scoped>
.calendar-container
width: calc(100vw - 80px)
</style>

View File

@@ -1,59 +0,0 @@
<template lang="pug">
.flex.flex-col(
:style="backgroundExtendedWidth"
)
.flex.flex-col
.line-wrapper
.line.flex.items-center(
v-for="hour in timeCoil",
:key="hour"
)
.middle-line
</template>
<script>
export default {
name: "CalendarBackground",
props: {
timeCoil: Array,
ownersCount: Number,
},
data() {
return {
columnWidth: 0,
defaultColumnWidth: 470,
pixelsPerHour: 62,
};
},
computed: {
backgroundExtendedWidth() {
if (this.ownersCount > 3) {
return {
width: `${this.defaultColumnWidth * this.ownersCount}px`,
};
}
return {
width: "100%",
};
},
},
};
</script>
<style lang="sass" scoped>
.header
height: 48px
.line
border-bottom: 1px solid var(--border-light-grey-color)
height: 62px
&:first-child
height: 63px
border-top: 1px solid var(--border-light-grey-color)
&:last-child
display: none
.middle-line
border-top: 1px dashed var(--border-light-grey-color)
width: 100%
</style>

View File

@@ -1,63 +0,0 @@
<template lang="pug">
.calendar-clock-column.flex.flex-col.items-end.gap-y-43.pb-5.px-3
span.text-base(
v-for="hour in timeCoil",
:key="hour",
:class="currentHourStyle(hour)"
) {{ hour }}
</template>
<script>
export default {
name: "CalendarClockColumn",
props: {
timeCoil: Array,
currentTime: String,
currentDate: Object,
dayEndTime: Number,
isCurrentDate: Boolean,
},
computed: {
currentHour() {
return this.convertTime(this.currentTime, 0, -6);
},
currentMinute() {
return this.convertTime(this.currentTime, 3, -3);
},
isEndDay() {
return this.dayEndTime === this.currentHour && this.currentMinute > 0;
},
},
methods: {
currentHourStyle(elem) {
if (
this.convertTime(elem, 0, 3) === this.currentHour &&
!this.isEndDay &&
this.isCurrentDate
) {
return {
"current-time": true,
"font-bold": true,
};
}
return {
"current-time": false,
"font-medium": true,
};
},
convertTime(str, startIndex, endIndex) {
return parseInt(str.slice(startIndex, endIndex), 10);
},
},
};
</script>
<style lang="sass" scoped>
.current-time
color: var(--bg-event-red-color)
.calendar-clock-column
width: 80px
height: 100%
color: var(--font-dark-blue-color)
background-color: var(--default-white)
</style>

View File

@@ -1,259 +0,0 @@
<template lang="pug">
.calendar-column-wrapper.flex.flex-col
.header.flex.items-center.justify-between.py-2.px-6.top-0
.flex.items-center
base-avatar.mr-2(:size="32", :color="ownerData.color")
img.h-full.object-cover(
:src="url + ownerData.photo",
alt="Team member",
v-if="ownerData.photo"
)
span(v-if="!ownerData.photo") {{ defaultAvatar }}
span.owner-name.font-medium.text-base.mr-6 {{ ownerName }}
img.icon-wrapper.cursor-pointer(src="@/assets/icons/lock.svg")
column-header-checkbox
.body(@dblclick="clickOnBackground")
//.nonworking-time(:style="nonworkingStartTime")
transition-group(name="card")
calendar-event-card(
v-for="event, index in dayEvents",
:key="event.id + index",
:id="event.id",
:ownerEvent="event",
:event-statuses="eventStatuses",
:style="eventCardPosition(event.start, event.end)",
@selected-event="transmitEventData",
@delete-event="transmitDeleteEvent",
:schedule-body-ref="scheduleBodyRef",
)
//.nonworking-time.absolute(:style="nonworkingEndTime")
</template>
<script>
import ColumnHeaderCheckbox from "./CalendarColumnHeaderCheckbox.vue";
import BaseAvatar from "@/components/base/BaseAvatar";
import CalendarEventCard from "./CalendarEventCard.vue";
//import * as moment from "moment/moment";
import TheNotificationProvider from "@/components/Notifications/TheNotificationProvider";
import { addNotification } from "@/components/Notifications/notificationContext";
export default {
name: "CalendarColumn",
components: {
CalendarEventCard,
BaseAvatar,
ColumnHeaderCheckbox,
TheNotificationProvider,
},
props: {
ownerData: Object,
dayEvents: Array,
dayEndTime: Number,
dayStartTime: Number,
eventStatuses: Array,
//changeFormWasClosed: Boolean,
scheduleBodyRef: Node,
url: String,
openFormCreateEvent: Function,
currentDate: {
type: Object,
default() {
return {};
},
},
},
data() {
return {
pixelsPerHour: 62,
};
},
computed: {
/*workingShift() {
if (!this.ownerData.id) return null;
let start = this.ownerData.start_time;
let end = this.ownerData.end_time;
return {
start: start?.split(":"),
end: end?.split(":"),
fulltime: `${start} - ${end}`,
};
},
nonworkingStartTime() {
if (!this.ownerData.id || !this.workingShift) return {};
let height =
(this.workingShift.start[0] - this.dayStartTime) * this.pixelsPerHour +
this.workingShift.start[1] * this.pixelsPerMinute;
if (height <= 0)
return {
height: 0,
display: "none",
};
return {
height: `${height}px`,
};
},
nonworkingEndTime() {
if (!this.ownerData.id || !this.workingShift) return {};
let time = moment()
.hour(this.dayEndTime)
.minute(0)
.subtract(parseInt(this.workingShift.end[0]), "hours")
.subtract(parseInt(this.workingShift.end[1]), "minutes")
.format("HH:mm")
.split(":");
let position =
time[0] * this.pixelsPerHour + time[1] * this.pixelsPerMinute;
if (position <= 0)
return {
bottom: 0,
display: "none",
};
return {
bottom: 0,
height: `${position}px`,
};
},*/
ownerName() {
if (this.ownerData.id) {
let checkedFirstName =
this.ownerData.first_name !== null
? this.ownerData.first_name[0] + "."
: "";
let checkedPatronymic =
this.ownerData.patronymic !== null
? this.ownerData.patronymic[0] + "."
: "";
return `${this.ownerData.last_name} ${checkedFirstName}${checkedPatronymic}`;
}
return null;
},
defaultAvatar() {
let checkedFirstName =
this.ownerData.first_name !== null
? this.ownerData.first_name[0]
: this.ownerData.last_name[1];
return `${this.ownerData.last_name[0]}${checkedFirstName}`;
},
pixelsPerMinute() {
return this.pixelsPerHour / 60;
},
},
methods: {
eventCardPosition(startTime, endTime) {
let start = startTime
.slice(11, -4)
.split(":")
.map((elem) => parseInt(elem, 10));
let end = endTime.slice(11, -6);
let position =
(start[0] - this.dayStartTime) * this.pixelsPerHour +
start[1] * this.pixelsPerMinute;
if (
parseInt(start[0], 10) < this.dayStartTime ||
parseInt(end, 10) > this.dayEndTime
) {
return {
top: "0px",
visibility: "hidden",
};
}
return {
top: `${position}px`,
};
},
transmitEventData(eventData) {
this.$emit("selected-event", eventData);
},
/*transmitResetChangeForm() {
this.$emit("reset-change-form");
},*/
transmitDeleteEvent(eventData) {
this.$emit("delete-event", eventData);
},
addErrorNotification(title, message) {
addNotification(title, title, message, "error", 5000);
},
/*clickOnBackground(e) {
let res = String((e.offsetY / this.pixelsPerHour).toFixed(2)).split(".");
let hours = parseInt(res[0]) + this.dayStartTime;
let minuts = Math.round(res[1] * 0.6);
if (minuts < 10) minuts = "0" + minuts;
if (hours < 10) hours = "0" + hours;
if (e.target.className !== "body")
this.addErrorNotification(
`Для сотрудника ${this.ownerName} рабочие часы: ${this.workingShift.fulltime}`,
"Вы пытаетесь добавить запись вне рабочего времени"
);
else
this.$emit("selected-event", {
employees: [
{
employee: {
id: this.ownerData.id,
last_name: this.ownerData.last_name,
first_name: this.ownerData.first_name,
patronymic: this.ownerData.patronymic,
color: this.ownerData.color,
photo: this.ownerData.photo,
},
role: "owner",
},
],
start: `${this.currentDate.format(
"YYYY-MM-DD"
)}T${hours}:${minuts}:00`,
end: `${this.currentDate.format("YYYY-MM-DD")}T${hours}:${minuts}:00`,
});
},*/
},
};
</script>
<style lang="sass" scoped>
.calendar-column-wrapper
border-right: 1px solid var(--border-light-grey-color)
&:last-child
border-right: none
.header
position: sticky
z-index: 5
background-color: var(--default-white)
.body
position: relative
z-index: 3
height: 100%
.btn
opacity: 0.5
padding: 7px 13px !important
.icon-wrapper
width: 24px
height: 24px
.owner-name
color: var(--font-dark-blue-color)
.card-enter-from
opacity: 0
pointer-events: none
.card-enter-active
transition: 0.3s ease
.card-leave-to
opacity: 0
pointer-events: none
.card-leave-active
transition: 0.3s ease
.card-move
transition: 0.3s ease
.nonworking-time
width: 100%
background-color: var(--btn-blue-sec-color)
opacity: 0.3
</style>

View File

@@ -1,42 +0,0 @@
<template lang="pug">
div.icon-wrap.flex.items-center.justify-center.cursor-pointer.py-1.px-2(
:class="{disable: !isChecked}",
@click="changeState"
)
.icon-doc-ok.text-xxl
</template>
<script>
export default {
name: "CalendarColumnHeaderCheckbox",
data() {
return {
isChecked: false,
};
},
methods: {
changeState() {
this.isChecked = !this.isChecked;
this.$emit("isChecked", this.isChecked);
},
},
};
</script>
<style lang="sass" scoped>
.disable
opacity: 0.5
.icon-wrap
height: 32px
background-color: var(--btn-blue-color-1)
color: var(--btn-blue-color)
border-radius: 4px
&:hover
background-color: var(--btn-blue-color-2)
&:active
background-color: var(--font-dark-blue-color)
color: var(--default-white)
.icon-doc-ok
width: 24px
height: 24px
</style>

View File

@@ -1,78 +0,0 @@
<template lang="pug">
.flex.flex-col.mb-1.mt-4
span.font-medium.text-base.modal-text.mb-3 Вы действительно хотите удалить это событие?
.card-container
calendar-event-description-card(
disabled,
:event-statuses="eventStatuses",
:owner-event="ownerEvent"
)
.flex.gap-x-3.mt-6.font-semibold
q-btn(
label="Отменить",
no-caps,
outline,
color="primary",
padding="10px 22px",
@click="closeModal"
)
q-btn(
label="Удалить",
no-caps,
outline,
style="color: var(--border-red-color)",
padding="10px 22px",
@click="deleteEvent"
)
</template>
<script>
import { fetchWrapper } from "@/shared/fetchWrapper.js";
import CalendarEventDescriptionCard from "./CalendarEventDescriptionCard.vue";
import TheNotificationProvider from "@/components/Notifications/TheNotificationProvider";
import { addNotification } from "@/components/Notifications/notificationContext";
export default {
name: "CalendarDeleteModal",
components: {
CalendarEventDescriptionCard,
TheNotificationProvider,
},
props: {
ownerEvent: Object,
eventStatuses: {
type: Array,
default() {
return [];
},
},
closeModal: Function,
},
methods: {
async deleteEvent() {
await fetchWrapper.del(`registry/event/${this.ownerEvent.id}/delete/`);
this.$emit("update-events");
this.closeModal();
this.addSuccessNotification();
},
addSuccessNotification() {
addNotification(
new Date().getTime(),
"Событие успешно удалено",
"",
"success",
5000
);
},
},
};
</script>
<style lang="sass" scoped>
.modal-text
color: var(--font-grey-color)
.card-container
width: 374px
border-radius: 4px
border: 1px solid var(--border-light-grey-color-1)
</style>

View File

@@ -1,348 +0,0 @@
<template lang="pug">
.wrapper.cursor-pointer.my-1.mx-1(
:style="themeColors",
:class="cardTheme",
ref="eventCard"
)
.card.flex.px-2(
:class="{'py-6px flex-col': longCard}",
:style="cardHeight",
@click="openDescriptionCard",
@dblclick.stop
)
.header.flex.justify-between.items-center(:class="{'items-start': longCard}")
.header-text
span.inline-block.align-middle.font-bold.text-base.mr-4 {{ eventTime }}
span.inline-block.align-middle.font-medium.text-base {{ eventMember }}
.details-count.flex.justify-center.items-center.text-xxs.font-medium(v-if="someDetailsShown") {{ `+${detailsCount}` }}
.body.flex.text-xxs.font-medium(v-if="longCard")
.col.mr-22px
ul
li.mt-2(v-for="elem in descriptionColumns.leftColumn" :key="elem") {{ elem }}
.col
ul
li.mt-2(v-for="elem in descriptionColumns.rightColumn" :key="elem") {{ elem }}
transition(name="description")
calendar-event-description-card(
v-if="isOpenDescriptionCard",
:owner-event="ownerEvent",
:event-statuses="eventStatuses",
@selected-event="transmitEventData",
@close-description-card="closeDescriptionCard",
@delete-event="transmitDeleteEvent",
:schedule-body-ref="scheduleBodyRef"
)
</template>
<script>
import CalendarEventDescriptionCard from "./CalendarEventDescriptionCard.vue";
export default {
name: "CalendarEventCard",
components: { CalendarEventDescriptionCard },
props: {
//changeFormWasClosed: Boolean,
ownerEvent: Object,
eventStatuses: {
type: Array,
default() {
return [];
},
},
scheduleBodyRef: Node,
},
data() {
return {
pixelsPerHour: 62,
isActive: false,
someDetailsShown: true,
isOpenDescriptionCard: false,
};
},
computed: {
pixelsPerMinute() {
return this.pixelsPerHour / 60;
},
eventTime() {
return `${this.trimTime(this.ownerEvent.start)} - ${this.trimTime(
this.ownerEvent.end
)}`;
},
eventMember() {
let membersArray = this.ownerEvent.members;
if (membersArray.length > 1) {
let primaryMember = membersArray.find(
(elem) => elem.role === "primary"
);
return this.composeFullName(primaryMember.person);
}
return this.composeFullName(membersArray[0].person);
},
calculateCardHeight() {
let startTime = this.trimTime(this.ownerEvent.start)
.split(":")
.map((elem) => parseInt(elem, 10));
let endTime = this.trimTime(this.ownerEvent.end)
.split(":")
.map((elem) => parseInt(elem, 10));
return (
(endTime[0] * 60 + endTime[1] - (startTime[0] * 60 + startTime[1])) *
this.pixelsPerMinute
);
},
cardHeight() {
return {
height: `${this.calculateCardHeight - 8}px`,
};
},
cardTheme() {
return {
"active-theme": this.isActive,
"default-theme": !this.isActive,
"long-card": this.longCard,
};
},
themeColors() {
switch (this.ownerEvent.status) {
case this.eventStatuses[1].value:
return {
"--bg-color": "var(--bg-event-grey-color-0)",
"--bg-active": this.eventStatuses[1].color,
"--bg-hover": "var(--bg-event-grey-color-1)",
"--font-color": "var(--font-black-color)",
"--font-active-color": "var(--default-white)",
"--count-color": this.eventStatuses[1].color,
};
case this.eventStatuses[2].value:
return {
"--bg-color": "var(--bg-event-yellow-color-0)",
"--bg-active": this.eventStatuses[2].color,
"--bg-hover": "var(--bg-event-yellow-color-1)",
"--font-color": "var(--font-black-color)",
"--font-active-color": "var(--font-black-color)",
"--count-color": "var(--font-black-color)",
};
case this.eventStatuses[3].value:
return {
"--bg-color": "var(--bg-event-orange-color-0)",
"--bg-active": this.eventStatuses[3].color,
"--bg-hover": "var(--bg-event-orange-color-1)",
"--font-color": "var(--font-black-color)",
"--font-active-color": "var(--font-black-color)",
"--count-color": "var(--font-black-color)",
};
case this.eventStatuses[4].value:
return {
"--bg-color": "var(--bg-event-blue-color-0)",
"--bg-active": this.eventStatuses[4].color,
"--bg-hover": "var(--bg-event-blue-color-1)",
"--font-color": "var(--font-black-color)",
"--font-active-color": "var(--default-white)",
"--count-color": this.eventStatuses[4].color,
};
case this.eventStatuses[5].value:
return {
"--bg-color": "var(--bg-event-green-color-0)",
"--bg-active": this.eventStatuses[5].color,
"--bg-hover": "var(--bg-event-green-color-1)",
"--font-color": "var(--font-black-color)",
"--font-active-color": "var(--default-white)",
"--count-color": this.eventStatuses[5].color,
};
case this.eventStatuses[6].value:
return {
"--bg-color": "var(--bg-event-red-color-0)",
"--bg-active": this.eventStatuses[6].color,
"--bg-hover": "var(--bg-event-red-color-1)",
"--font-color": "var(--font-black-color)",
"--font-active-color": "var(--default-white)",
"--count-color": this.eventStatuses[6].color,
};
default:
return {
"--bg-color": "var(--default-white)",
"--bg-active": "var(--btn-blue-color)",
"--bg-hover": "var(--bg-event-default-hover-color)",
"--font-color": "var(--font-black-color)",
"--font-active-color": "var(--default-white)",
"--border-color": "#b3c3f3",
"--border-active-color": "var(--btn-blue-color)",
"--count-color": "var(--btn-blue-color)",
};
}
},
description() {
return this.ownerEvent.description
? this.ownerEvent.description.split(", ")
: [];
},
descriptionColumns() {
let leftCol = [],
rightCol = [],
heightParts = parseInt((this.calculateCardHeight - 8) / 23);
if (this.ownerEvent.description && heightParts > 1) {
let n = heightParts;
for (let i = 0; i < n * 2 - 2; i++) {
if (!this.description[i]) break;
i % 2 === 0
? leftCol.push(this.description[i])
: rightCol.push(this.description[i]);
}
}
return {
leftColumn: leftCol,
rightColumn: rightCol,
};
},
longCard() {
return parseInt((this.calculateCardHeight - 8) / 23) > 1 ? true : false;
},
detailsCount() {
let columnsLength =
this.descriptionColumns.leftColumn.length +
this.descriptionColumns.rightColumn.length,
remainingDetails = this.description.length - columnsLength;
if (!remainingDetails) this.changeDetailsShown();
return remainingDetails;
},
},
methods: {
trimTime(time) {
return time.slice(11, 16);
},
composeFullName(object) {
return `${object.last_name} ${object.first_name ?? ""} ${
object.patronymic ?? ""
}`;
},
setActiveTheme() {
this.isActive = true;
},
setDefaultTheme() {
this.isActive = false;
},
changeDetailsShown() {
this.someDetailsShown = false;
},
transmitEventData() {
this.$emit("selected-event", this.ownerEvent);
this.hideDescriptionCard();
},
transmitDeleteEvent() {
this.$emit("delete-event", this.ownerEvent);
},
hideDescriptionCard() {
this.isOpenDescriptionCard = false;
},
openDescriptionCard() {
if (!this.isOpenDescriptionCard && !this.isActive) {
this.isOpenDescriptionCard = true;
this.setActiveTheme();
}
},
closeDescriptionCard() {
this.hideDescriptionCard();
this.setDefaultTheme();
},
},
/*watch: {
changeFormWasClosed: {
immediate: true,
handler(newValue) {
if (newValue === true && this.isActive) {
this.isActive = false;
this.$emit("reset-change-form");
}
},
},
},*/
};
</script>
<style lang="sass" scoped>
.description-enter-from
opacity: 0
transform: translateY(2px)
pointer-events: none
.description-enter-active
transition: 0.5s ease
.description-leave-to
opacity: 0
transform: translateY(2px)
pointer-events: none
.description-leave-active
transition: 0.5s ease
.description-move
transition: 0.5s ease
.wrapper
position: absolute
width: calc(100% - 8px)
.default-theme
z-index: 2
.card
background-color: var(--bg-color)
border: 2px solid var(--border-color)
border-left: 4px solid var(--bg-active)
.card:hover
background-color: var(--bg-hover)
.header-text, .body
color: var(--font-color)
.details-count
background-color: var(--bg-active)
color: var(--font-active-color)
li:before
background-color: var(--font-color)
.active-theme
z-index: 3
.card
background-color: var(--bg-active)
border: 2px solid var(--border-active-color)
border-left: 4px solid var(--bg-active)
.header-text, .body
color: var(--font-active-color)
.details-count
background-color: var(--default-white)
color: var(--count-color)
li:before
background-color: var(--font-active-color)
.card
border-radius: 4px
min-height: 23px
.header
width: 100%
.details-count
width: 24px
height: 16px
border-radius: 16px
.col
max-width: calc(462px/2 - 22px)
ul
list-style-type: none
margin: 0
padding: 0
li
overflow: hidden
text-overflow: ellipsis
white-space: nowrap
li:before
content: ''
display: inline-block
height: 8px
width: 8px
border-radius: 50%
background-color : var(--font-black-color)
margin-right: 4px
</style>

View File

@@ -1,214 +0,0 @@
<template lang="pug">
.wrapper.px-4.pt-14px.pb-4.font-medium.cursor-auto(
:style="{...typeColor, ...position, ...constantWidth}",
v-click-outside="close",
:class="{'shadow': !disabled}",
ref="descriptionCard"
)
.flex.justify-between.items-center.mb-2
.flex
span.inline-block.font-bold.text-base.mr-3.mt-2px {{ eventTime }}
.type.text-xs.font-medium.flex.items-center.justify-center.px-14px(v-if="isCertainType")
span.type-text {{ status }}
.right-side.flex.gap-x-4.text-sm(v-if="!disabled")
.icon-basket.flex.items-center.cursor-pointer(@click="transmitDeleteEvent")
.icon-edit.flex.items-center.cursor-pointer(@click="transmitEventData")
.icon-cancel.text-xxs.flex.items-center.cursor-pointer(@click="close")
.body.mr-6
span.text-base {{ eventMember }}
.flex.text-xxs.justify-between(
v-if="!disabled",
:class="{'mt-4': description.length > 0}"
)
.column
ul
li(v-for="elem in description" :key="elem") {{ elem }}
</template>
<script>
import { statusesConfig } from "@/pages/oldCalendar/utils/statusesConfig";
export default {
name: "CalendarEventDescriptionCard",
props: {
ownerEvent: Object,
eventStatuses: {
type: Array,
default() {
return [];
},
},
disabled: {
type: Boolean,
default: false,
},
scheduleBodyRef: Node,
},
data() {
return {
isCertainType: true,
position: {},
};
},
computed: {
status() {
return this.ownerEvent.status
? statusesConfig.find((e) => e.value === this.ownerEvent.status).label
: "";
},
typeColor() {
switch (this.ownerEvent.status) {
case this.eventStatuses[1].value:
return {
"--bg-color": "var(--bg-event-grey-color-0)",
"--font-color": this.eventStatuses[1].color,
};
case this.eventStatuses[2].value:
return {
"--bg-color": "var(--bg-event-yellow-color-0)",
"--font-color": "var(--font-black-color)",
};
case this.eventStatuses[3].value:
return {
"--bg-color": "var(--bg-event-orange-color-0)",
"--font-color": "var(--font-black-color)",
};
case this.eventStatuses[4].value:
return {
"--bg-color": "var(--bg-event-blue-color-0)",
"--font-color": this.eventStatuses[4].color,
};
case this.eventStatuses[5].value:
return {
"--bg-color": "var(--bg-event-green-color-0)",
"--font-color": this.eventStatuses[5].color,
};
case this.eventStatuses[6].value:
return {
"--bg-color": "var(--bg-event-red-color-0)",
"--font-color": this.eventStatuses[6].color,
};
default:
return {
"--bg-color": "var(--bg-event-default-hover-color)",
"--font-color": "var(--btn-blue-color)",
};
}
},
eventTime() {
return `${this.trimTime(this.ownerEvent.start)} - ${this.trimTime(
this.ownerEvent.end
)}`;
},
eventMember() {
let membersArray = this.ownerEvent.members;
if (membersArray.length > 1) {
let primaryMember = membersArray.find(
(elem) => elem.role === "primary"
);
return this.composeFullName(primaryMember.person);
}
return this.composeFullName(membersArray[0].person);
},
description() {
return this.ownerEvent.description
? this.ownerEvent.description.split(", ")
: [];
},
constantWidth() {
if (!this.disabled) {
return {
width: "426px !important",
};
}
return "";
},
},
methods: {
changeType() {
this.isCertainType = false;
},
transmitEventData() {
this.$emit("selected-event");
},
transmitDeleteEvent() {
this.$emit("delete-event");
},
close() {
if (!this.disabled) this.$emit("close-description-card");
},
trimTime(time) {
return time.slice(11, 16);
},
composeFullName(object) {
return `${object.last_name} ${object.first_name ?? ""} ${
object.patronymic ?? ""
}`;
},
},
mounted() {
if (!this.$refs.descriptionCard || !this.scheduleBodyRef) return;
const cardRect = this.$refs.descriptionCard.getBoundingClientRect();
const bodyRect = this.scheduleBodyRef.getBoundingClientRect();
const bodyHeight = this.scheduleBodyRef.clientHeight + bodyRect.y - 20;
if (cardRect.y + cardRect.height > bodyHeight) {
this.position = {
top: `-${cardRect.height + 8}px`,
};
} else
this.position = {
"margin-top": "8px",
};
},
};
</script>
<style lang="sass" scoped>
.wrapper
height: auto !important
background-color: var(--default-white)
color: var(--font-dark-blue-color)
.shadow
border-radius: 4px
box-shadow: var(--default-shadow)
.type
background-color: var(--bg-color)
border-radius: 4px
.type-text
color: var(--font-color)
.right-side
height: 16px
color: var(--btn-blue-color)
.icon-basket
color: var(--border-red-color)
.column
max-width: calc(462px/2 - 36px)
ul
list-style-type: none
margin: 0
padding: 0
li
margin-bottom: 8px
overflow: hidden
text-overflow: ellipsis
white-space: nowrap
&:last-child
margin: 0px
li:before
content: ''
display: inline-block
height: 6px
width: 6px
border-radius: 50%
background-color: var(--font-black-color)
margin: 0 8px 1px 0
</style>

View File

@@ -1,648 +0,0 @@
<template lang="pug">
base-modal(
v-model="value",
:title="!selectedEventData.id ? 'Запись на прием' : 'Изменение записи'",
draggable,
hide-overlay
)
.event-form.flex.flex-col.gap-y-6.pt-8
.flex.flex-col.gap-y-8
base-select(
v-if="selectedEventData.id"
v-model="status",
:items="statusesList",
label="Статус приема",
size="M"
)
base-input(
v-else,
disabled,
v-model="status.label",
label="Статус приема",
size="M"
)
base-select(
:items="ownersList",
v-model="employees.employee",
placeholder="Выберите сотрудника"
label="Сотрудник",
size="M"
)
base-select(
:items="membersList",
v-model="members.person",
placeholder="Выберите клиента",
label="Клиент",
size="M"
)
.flex.gap-x-4
base-input(
v-model="eventDate",
label="Дата",
type="date",
size="M"
)
.flex.gap-x-2.items-center.justify-between
base-input(
type="time",
v-model="startTime",
label="Начало",
size="M"
)
span.mt-4
base-input(
type="time",
v-model="endTime",
label="Конец",
size="M"
)
q-btn.create-button(
v-if="!selectedEventData.id",
label="Создать событие",
no-caps,
color="primary",
padding="10px 22px",
:disabled="disabledCreateButton",
@click="sendEventData",
)
q-btn.update-button(
v-else,
label="Сохранить",
no-caps,
color="primary",
padding="10px 22px",
:disabled="disabledUpdateButton",
@click="updateEventData",
)
</template>
<script>
import TheNotificationProvider from "@/components/Notifications/TheNotificationProvider";
import { addNotification } from "@/components/Notifications/notificationContext";
import { fetchWrapper } from "@/shared/fetchWrapper.js";
import BaseInput from "@/components/base/BaseInput.vue";
import BaseSelect from "@/components/base/BaseSelect.vue";
import * as moment from "moment/moment";
import BaseModal from "@/components/base/BaseModal.vue";
import { v_model } from "@/shared/mixins/v-model";
import { statusesConfig } from "@/pages/oldCalendar/utils/statusesConfig";
export default {
name: "FormChangeEvent",
components: {
BaseModal,
BaseSelect,
BaseInput,
TheNotificationProvider,
},
mixins: [v_model],
//emits: ["clear-selected-event-data", "close-change-form"],
emits: ["clear-event-data"],
props: {
//closeForm: Function,
selectedEventData: {
type: Object,
default() {
return {};
},
},
timeInformation: {
type: Object,
default() {
return {};
},
},
currentDate: {
type: Object,
default() {
return {};
},
},
},
data() {
return {
EMPLOYEE_TYPE: "owner",
MEMBER_TYPE: "primary",
EVENT_KIND: "Прием",
eventData: {},
startTime: "",
endTime: "",
members: {},
employees: {},
eventDate: Date,
status: { label: "Планируется прием", id: null },
id: null,
//ifClearedForm: true,
membersData: [],
ownersData: [],
WORKING_STATUS: "WORKS",
};
},
computed: {
ownersList() {
if (this.ownersData) {
let filteredArray = [];
this.ownersData.forEach((elem) => {
filteredArray.push({
id: elem.id,
label: this.trimOwnerName(
elem.last_name,
elem.first_name,
elem.patronymic
),
});
});
return filteredArray;
}
return [];
},
membersList() {
if (this.membersData) {
let filteredArray = [];
this.membersData.forEach((elem) => {
filteredArray.push({
id: elem.id,
label: this.trimMemberName(
elem.last_name,
elem.first_name,
elem.patronymic
),
});
});
return filteredArray;
}
return [];
},
statusesList() {
let filteredArray = [];
statusesConfig.forEach((elem) => {
filteredArray.push({
id: elem.id,
label: elem.label,
});
});
return filteredArray;
},
disabledCreateButton() {
if (
this.eventDate &&
this.startTime &&
this.endTime &&
this.members.person.label &&
this.employees.employee.label &&
this.status.label
) {
return false;
}
return true;
},
disabledUpdateButton() {
let start = moment(this.selectedEventData.start);
let end = moment(this.selectedEventData.end);
if (
moment(this.eventDate).format("YYYY-MM-DD") ===
start.format("YYYY-MM-DD") &&
this.startTime === start.format("HH:mm") &&
this.endTime === end.format("HH:mm") &&
this.findStatus(this.status.label) === this.selectedEventData.status &&
this.employees.employee.id ===
this.personId(this.selectedEventData.employees, "employee") &&
this.members.person.id ===
this.personId(this.selectedEventData.members, "person")
) {
return true;
}
if (!this.eventDate) return true;
return false;
},
startDate() {
return this.selectedEventData.start
? new Date(this.selectedEventData.start)
: this.currentDate.toDate();
},
endDate() {
return this.selectedEventData.end
? new Date(this.selectedEventData.end)
: this.currentDate.toDate();
},
eventEmployee() {
if (this.selectedEventData.employees) {
let foundEmployee = this.selectedEventData.employees.find(
({ role }) => role === this.EMPLOYEE_TYPE
);
let {
employee: { id, last_name, first_name, patronymic },
...rest
} = foundEmployee;
return {
employee: {
id: id,
label: this.trimOwnerName(last_name, first_name, patronymic),
},
id: rest?.id || null,
role: rest?.role || this.EMPLOYEE_TYPE,
};
}
return {
employee: {
id: null,
label: "",
},
role: this.EMPLOYEE_TYPE,
};
},
eventMember() {
if (this.selectedEventData.members) {
let foundMember = {};
if (this.selectedEventData.members.length > 1) {
foundMember = this.selectedEventData.members.find(
({ role }) => role === this.MEMBER_TYPE
);
} else foundMember = this.selectedEventData.members[0];
let {
person: { id, last_name, first_name, patronymic },
...rest
} = foundMember;
return {
person: {
id: id,
label: this.trimMemberName(last_name, first_name, patronymic),
},
id: rest.id,
role: rest?.role || this.MEMBER_TYPE,
};
}
return {
person: {
id: null,
label: "",
},
role: this.MEMBER_TYPE,
};
},
eventId() {
return this.selectedEventData.id ? this.selectedEventData.id : "";
},
eventStatus() {
return this.selectedEventData.status
? {
label: statusesConfig.find(
(e) => e.value === this.selectedEventData.status
).label,
id: statusesConfig.find(
(e) => e.value === this.selectedEventData.status
).id,
}
: {
label: statusesConfig.find((e) => e.value === "PLANNED").label,
id: 0,
};
},
},
methods: {
findStatus(value) {
return statusesConfig.find((e) => e.label === value)?.value;
},
checkTimeLimits(eventTime, scheduleTime, timeType) {
if (timeType === "start")
return (
parseInt(eventTime.split(":")[0]) <
parseInt(scheduleTime.split(":")[0])
);
if (timeType === "end") {
let firstTime = eventTime.split(":").map((elem) => parseInt(elem));
let secondTime = scheduleTime.split(":").map((elem) => parseInt(elem));
if (firstTime[0] === secondTime[0]) return firstTime[1] > secondTime[1];
else return firstTime[0] > secondTime[0];
}
},
checkTime() {
let counter = 0;
let foundedSchedule = {};
let foundedEmployee = this.ownersData.find(
(elem) => elem.id === this.employees.employee.id
);
if (foundedEmployee)
foundedSchedule = foundedEmployee.schedules.find(
(elem) =>
elem.date === moment(this.eventDate).format("YYYY-MM-DD") &&
elem.status === this.WORKING_STATUS
);
if (!foundedSchedule) {
this.addErrorNotification(
"Некорректная дата события",
`В данный день ${this.employees.employee.label} не работает`
);
counter += 1;
return false;
}
if (
this.checkTimeLimits(
this.startTime,
foundedSchedule.start_time,
"start"
)
) {
this.addErrorNotification(
"Некорректное время начала события",
"Время начала события раньше начала рабочего дня"
);
counter += 1;
}
if (this.checkTimeLimits(this.endTime, foundedSchedule.end_time, "end")) {
this.addErrorNotification(
"Некорректное время окончания события",
"Время окончания события позже окончания рабочего дня"
);
counter += 1;
}
if (
this.checkTimeLimits(this.endTime, foundedSchedule.start_time, "start")
) {
this.addErrorNotification(
"Некорректное время окончания события",
"Время окончания события раньше начала рабочего дня"
);
counter += 1;
}
if (
this.checkTimeLimits(this.startTime, foundedSchedule.end_time, "end")
) {
this.addErrorNotification(
"Некорректное время начала события",
"Время начала события позже окончания рабочего дня"
);
counter += 1;
}
return counter === 0;
},
trimOwnerName(lastName, firstName, patronymic) {
let checkedFirstName = firstName !== null ? firstName[0] + "." : "";
let checkedPatronymic = patronymic !== null ? patronymic[0] + "." : "";
return `${lastName} ${checkedFirstName}${checkedPatronymic}`;
},
trimMemberName(lastName, firstName, patronymic) {
return `${lastName} ${firstName ?? ""} ${patronymic ?? ""}`;
},
async sendEventData() {
if (!this.checkTime()) return;
this.eventData = {
start: this.mergeDate(this.eventDate, this.startTime),
end: this.mergeDate(this.eventDate, this.endTime),
status: this.findStatus(this.status.label),
kind: this.EVENT_KIND,
employees: [
this.findPerson(this.ownersData, this.employees, "employee"),
],
members: [this.findPerson(this.membersData, this.members, "person")],
};
await this.postCreateEvent(this.eventData);
this.eventData = {};
this.value = false;
},
async updateEventData() {
if (!this.checkTime()) return;
if (
Object.keys(
this.findPerson(this.ownersData, this.employees, "employee")
).length > 0 &&
Object.keys(this.findPerson(this.membersData, this.members, "person"))
.length > 0
) {
this.eventData = {
start: this.mergeDate(this.eventDate, this.startTime),
end: this.mergeDate(this.eventDate, this.endTime),
status: this.findStatus(this.status.label),
employees: [
this.findPerson(this.ownersData, this.employees, "employee"),
],
members: [this.findPerson(this.membersData, this.members, "person")],
};
await this.postUpdateEvent(this.id, this.eventData);
this.eventData = {};
this.value = false;
} else
this.addErrorNotification(
"Событие не может быть изменено",
"Клиент был удален"
);
},
//clearForm() {
// if (this.selectedEventData.id) this.$emit("close-change-form");
// this.$emit("clear-selected-event-data");
// this.closeForm();
//},
mergeDate(eventDate, time) {
let parseTime = time.split(":");
return moment(eventDate.setHours(parseTime[0], parseTime[1]), 0).format(
"YYYY-MM-DDTHH:mm:ss"
);
},
findPerson(requestedList, object, field) {
let foundPerson = requestedList.find(({ id }) => id === object[field].id);
if (foundPerson) {
let returnedData = {
[field]: {
id: foundPerson.id,
last_name: foundPerson.last_name,
first_name: foundPerson.first_name,
patronymic: foundPerson.patronymic,
color: foundPerson.color,
},
role: object.role,
};
if (object.id) returnedData.id = object.id;
return returnedData;
}
return {};
},
async postCreateEvent(event) {
const response = await fetchWrapper.post("registry/event/create/", event);
if (response.type && response.type === "validation_error") {
this.addErrorNotification(
response.errors[0].code,
response.errors[0].detail
);
} else {
this.addSuccessNotification("Событие успешно создано");
//this.clearForm();
}
},
async postUpdateEvent(id, event) {
const response = await fetchWrapper.post(
`registry/event/${id}/update/`,
event
);
if (response.type && response.type === "validation_error") {
this.addErrorNotification(
response.errors[0].code,
response.errors[0].detail
);
} else {
this.addSuccessNotification("Изменения успешно сохранены");
//this.clearForm();
}
},
personId(object, field) {
if (object) {
let foundPerson = {};
if (field === "employee") {
foundPerson = object.find(({ role }) => role === this.EMPLOYEE_TYPE);
}
if (field === "person") {
if (object.length > 1) {
foundPerson = object.find(({ role }) => role === this.MEMBER_TYPE);
}
foundPerson = object[0];
}
let {
[field]: { id },
} = foundPerson;
return id;
}
},
addErrorNotification(title, message) {
addNotification(title, title, message, "error", 5000);
},
addSuccessNotification(message) {
addNotification(new Date().getTime(), message, "", "success", 5000);
},
fetchMembersData() {
fetchWrapper
.get("general/person/?limit=200")
.then((res) => this.saveMembersData(res));
},
fetchOwnersData() {
fetchWrapper
.get(
`accounts/schedules/?date_after=${this.currentDate
.clone()
.subtract(1, "month")
.format("YYYY-MM-DD")}&date_before=${this.currentDate
.clone()
.add(1, "month")
.format("YYYY-MM-DD")}`
)
.then((res) => this.saveOwnersData(res));
},
saveOwnersData(res) {
let serializedList = [];
if (res.results.length === 0) {
this.ownersData = [];
return;
}
res.results.forEach((elem) => {
let foundedElem = serializedList.find(
(item) => item.id === elem.employee.id
);
if (!foundedElem) {
serializedList.push({
id: elem.employee.id,
last_name: elem.employee.last_name,
first_name: elem.employee.first_name,
patronymic: elem.employee.patronymic,
color: elem.employee.color,
schedules: [
{
date: elem.date,
end_time: elem.end_time?.slice(0, elem.end_time.length - 3),
start_time: elem.start_time?.slice(
0,
elem.start_time.length - 3
),
status: elem.status,
id: elem.id,
},
],
});
} else {
foundedElem.schedules.push({
date: elem.date,
end_time: elem.end_time?.slice(0, elem.end_time.length - 3),
start_time: elem.start_time?.slice(0, elem.start_time.length - 3),
status: elem.status,
id: elem.id,
});
}
});
this.ownersData = serializedList;
},
saveMembersData(res) {
this.membersData = res.results;
},
},
watch: {
startDate: {
immediate: true,
handler(newDate) {
if (newDate) {
this.eventDate = newDate;
this.startTime = moment(newDate).format("HH:mm");
}
},
},
endDate: {
immediate: true,
handler(newDate) {
if (newDate) {
this.endTime = moment(newDate).format("HH:mm");
}
},
},
selectedEventData: {
immediate: true,
handler(newDate) {
if (newDate) {
this.id = this.eventId;
this.status = this.eventStatus;
this.employees = this.eventEmployee;
this.members = this.eventMember;
}
},
},
value(newValue) {
if (newValue === false) this.$emit("clear-event-data");
},
},
mounted() {
this.fetchOwnersData();
this.fetchMembersData();
},
};
</script>
<style lang="sass" scoped>
.event-form
width: 550px
.form-item
border-radius: 4px
width: 344px
background-color: var(--default-white)
.date-input
width: 174px
border-radius: 4px
background-color: var(--default-white)
.icon
width: 24px
height: 24px
color: var(--font-dark-blue-color)
.close-icon
color: var(--font-dark-blue-color)
&:hover
color: var(--btn-blue-color)
.create-button
width: 183px
.select
border: 1.5px solid var(--border-light-grey-color)
border-radius: 4px
.update-button
width: 132px
</style>

View File

@@ -1,124 +0,0 @@
<template lang="pug">
.calendar-header-wrapper.flex.items-center.justify-between.py-4.pl-4.pr-6
.flex.items-center
q-btn.mr-4(
color="secondary",
round,
size="14px",
dense,
icon="arrow_back_ios_new"
text-color="primary",
@click="previousHandler"
)
q-btn.mr-4(
color="secondary",
icon="arrow_forward_ios",
round,
size="14px",
text-color="primary",
dense,
@click="nextHandler"
)
.text.flex.items-center
span.font-medium.text-base {{ dateString }}
span.today.font-bold.text-xxs(v-if="isCurrentDate") Сегодня
//q-btn(
color="blue-grey-1",
round,
size="14px",
text-color="primary",
@click="previousHandler"
icon="arrow_back_ios_new"
//)
//.text.flex.items-center.mx-4
span.font-medium.text-base {{ dateString }}
span.today.font-bold.text-xxs(v-if="isCurrentDate") Сегодня
//q-btn(
color="blue-grey-1",
icon="arrow_forward_ios",
round,
size="14px",
text-color="primary",
@click="nextHandler"
//)
//.flex.gap-x-4.ml-5.border-2.border-green-600.bg-lime-200.items-center
.w-120.h-10.flex.items-center {{ date }}
base-date-picker(v-model="date")
calendar-layout-switch(@selected="changeSelectedLayout")
</template>
<script>
import CalendarLayoutSwitch from "./CalendarLayoutSwitch.vue";
//import BaseDatePicker from "@/components/base/BaseDatePicker.vue";
export default {
name: "CalendarHeader",
components: { CalendarLayoutSwitch },
props: {
currentDate: Object,
isCurrentDate: Boolean,
},
/*data() {
return {
date: new Date(),
};
},*/
computed: {
dateString() {
let newStr = this.currentDate.format("D MMMM YYYY");
return newStr
.split(" ")
.map((elem, index) => {
if (index === 1) return elem[0].toUpperCase() + elem.slice(1);
return elem;
})
.join(" ");
},
},
methods: {
changeSelectedLayout(option) {
this.$emit("selected-layout", option);
},
previousHandler() {
this.$emit("previous-date");
},
nextHandler() {
this.$emit("next-date");
},
},
};
</script>
<style lang="sass" scoped>
.calendar-header-wrapper
width: 100%
background-color: var(--default-white)
height: 72px
border-radius: 4px
z-index: 10
.left-arrow
padding: 3px 4px 0 4px !important
transform: rotate(90deg)
.right-arrow
padding: 3px 4px 0 4px !important
transform: rotate(270deg)
.text
color: var(--font-dark-blue-color)
.today
opacity: 0.5
.bg-blue-grey-1
background: var(--bg-light-grey) !important
//.text-primary
// color: var(--font-dark-blue-color-0) !important
.q-btn--round
width: 32px !important
height: 32px !important
min-width: 32px !important
min-height: 32px !important
</style>

View File

@@ -1,52 +0,0 @@
<template lang="pug">
.layout-switch-wrapper.flex.h-10.p-1
button#day.flex.items-center.px-3.transition.duration-200.ease-linear(
:class="dayLayoutState",
@click="changeSelectedLayout"
) День
button#week.flex.items-center.px-3.transition.duration-200.ease-linear(
:class="weekLayoutState",
@click="changeSelectedLayout"
) Неделя
</template>
<script>
export default {
name: "CalendarLayoutSwitch",
data() {
return {
selectedLayout: "day",
};
},
computed: {
dayLayoutState() {
return {
active: this.selectedLayout === "day",
};
},
weekLayoutState() {
return {
active: this.selectedLayout === "week",
};
},
},
methods: {
changeSelectedLayout(event) {
this.selectedLayout = event.target.id;
this.$emit("selected", event.target.id);
},
},
};
</script>
<style lang="sass" scoped>
.active
background-color: var(--bg-aqua-blue)
color: var(--default-white)
border-radius: 4px
.layout-switch-wrapper
background-color: var(--bg-light-grey)
color: var(--font-grey-color)
border-radius: 4px
</style>

View File

@@ -1,424 +0,0 @@
<template lang="pug">
.schedule.mx-2.pb-22px(
ref="schedule",
:style="scheduleWidth"
)
calendar-header(
:current-date="currentDate",
:is-current-date="isCurrentDate",
@previous-date="previousDate",
@next-date="nextDate",
@selected-layout="selectedLayout"
)
.schedule-body(
@scroll="changeScrollingState",
ref="scheduleBody"
)
.hiding-container.fixed(v-if="isScrolling")
.column-wrapper.flex.ml-20(:style="columnWrapperWidth")
calendar-column(
v-for="(owner, index) in filteredOwners",
:key="owner.id",
:owner-data="owner",
:url="url",
:day-events="filterEventsByOwner(owner)",
:day-start-time="validateStartTime",
:day-end-time="validateEndTime",
:style="columnSize",
:event-statuses="eventStatuses",
@selected-event="transmitEventData",
@delete-event="transmitDeleteEvent",
:schedule-body-ref="$refs.scheduleBody",
:open-form-create-event="openFormCreateEvent",
:current-date="currentDate"
)
.flex.w-full.relative
.time-coil-wrapper.left-0.-mt-12.pt-9
calendar-clock-column(
:timeCoil="timeCoil",
:current-time="currentTime",
:is-current-date="isCurrentDate",
:day-end-time="validateEndTime"
)
.time-circle-indicator.left-74px(
v-if="isShownIndicator",
:style="circleIndicatorLocation"
)
span.time-line-indicator.block.left-20(
v-if="isShownIndicator",
:style="lineIndicatorLocation"
)
.flex(:class="calendarBackgroundWidth")
calendar-background(
:time-coil="timeCoil",
:owners-count="ownersCount"
)
</template>
<script>
import * as moment from "moment/moment";
import CalendarHeader from "./CalendarHeader.vue";
import CalendarBackground from "./CalendarBackground.vue";
import CalendarClockColumn from "./CalendarClockColumn.vue";
import CalendarColumn from "./CalendarColumn.vue";
export default {
name: "CalendarSchedule",
components: {
CalendarHeader,
CalendarBackground,
CalendarClockColumn,
CalendarColumn,
},
props: {
openFormCreateEvent: Function,
url: String,
//changeFormWasClosed: Boolean,
currentDate: {
type: Object,
default() {
return {};
},
},
timeInformation: {
type: Object,
default() {
return {};
},
},
eventsData: {
type: Array,
default() {
return [];
},
},
sidebarWidth: String,
schedulesData: {
type: Array,
default() {
return [];
},
},
eventStatuses: {
type: Array,
default() {
return [];
},
},
},
data() {
return {
currentTime: "",
timeCoil: [],
timer: null,
isCurrentDate: true,
isShownIndicator: true,
pixelsPerHour: 62,
columnHeaderHeight: 48,
defaultColumnWidth: 470,
isScrolling: false,
};
},
computed: {
hours() {
return this.convertTime(this.currentTime, 0, -6);
},
minutes() {
return this.convertTime(this.currentTime, 3, -3);
},
hoursMinutes() {
return this.currentTime.slice(0, -3);
},
validateStartTime() {
return this.verifyTime(this.timeInformation.dayStartTime);
},
validateEndTime() {
return this.verifyTime(this.timeInformation.dayEndTime);
},
lineIndicatorLocation() {
if (this.ownersCount > 3) {
return {
width: `${this.defaultColumnWidth * this.ownersCount}px`,
top: `${this.calculateIndicatorLocation()}px`,
};
}
return {
width: "calc(100% - 80px)",
top: `${this.calculateIndicatorLocation()}px`,
};
},
circleIndicatorLocation() {
return {
top: `${this.calculateIndicatorLocation() + 42}px`,
};
},
pixelsPerMinute() {
return this.pixelsPerHour / 60;
},
scheduleHeight() {
return (
(this.validateEndTime - this.validateStartTime) * this.pixelsPerHour
);
},
scheduleWidth() {
return {
"--sidebar-width": this.sidebarWidth,
};
},
filteredOwners() {
let filteredArray = [];
this.schedulesData.forEach((elem) => {
filteredArray.push({
id: elem.employee?.id,
last_name: elem.employee?.last_name,
first_name: elem.employee?.first_name,
patronymic: elem.employee?.patronymic,
color: elem.employee?.color,
photo: elem.employee?.photo,
end_time: elem.end_time?.slice(0, elem.end_time.length - 3),
start_time: elem.start_time?.slice(0, elem.start_time.length - 3),
});
});
return filteredArray;
},
ownersCount() {
return this.filteredOwners.length;
},
columnHeight() {
return (
(this.timeCoil.length - 1) * this.pixelsPerHour +
this.columnHeaderHeight
);
},
columnSize() {
if (this.ownersCount > 3) {
return {
height: `${this.columnHeight}px`,
width: `${this.defaultColumnWidth}px`,
};
}
return {
height: `${this.columnHeight}px`,
width: `calc(100% / ${this.ownersCount})`,
};
},
columnWrapperWidth() {
if (this.ownersCount > 3) {
return {
width: `${this.defaultColumnWidth * this.ownersCount}px`,
};
}
return {
width: "calc(100% - 80px)",
};
},
calendarBackgroundWidth() {
return {
"w-full": this.ownersCount <= 3,
};
},
},
methods: {
previousDate() {
this.$emit("previous-date");
},
nextDate() {
this.$emit("next-date");
},
selectedLayout(option) {
this.$emit("selected-layout", option);
},
startTimer() {
if (
this.hours >= this.validateStartTime &&
this.hours < this.validateEndTime
) {
this.timer = setInterval(() => {
this.changeCurrentTime();
this.changeTimeCoil();
}, 5000);
}
},
stopTimer() {
clearInterval(this.timer);
this.timer = null;
},
changeCurrentTime() {
this.currentTime = moment().format("HH:mm:ss");
},
timeCoilInitialization() {
this.timeCoil = [];
for (let i = this.validateStartTime; i <= this.validateEndTime; i++) {
if (
i === this.hours &&
this.hours !== this.validateEndTime &&
this.isCurrentDate
) {
this.timeCoil.push(this.hoursMinutes);
} else this.timeCoil.push(`${i}:00`);
}
},
changeTimeCoil() {
this.timeCoil = this.timeCoil.map((elem) => {
if (this.convertTime(elem, 0, -3) === this.hours) {
return this.hoursMinutes;
}
return elem.slice(0, -3) + ":00";
});
},
verifyTime(dayTime) {
let timeArray = dayTime.split(":").map((elem) => parseInt(elem, 10));
let newTime = timeArray[1] > 30 ? timeArray[0] + 1 : timeArray[0];
return newTime;
},
convertTime(str, startIndex, endIndex) {
return parseInt(str.slice(startIndex, endIndex), 10);
},
calculateIndicatorLocation() {
let newTime = this.currentTime
.split(":")
.map((elem) => parseInt(elem, 10));
let result =
(newTime[0] - this.validateStartTime) * this.pixelsPerHour +
newTime[1] * this.pixelsPerMinute;
if (result > this.scheduleHeight || result < 0) {
this.isShownIndicator = false;
return 0;
}
return result;
},
findObjectInArray(array, object) {
return array.find(
(item) => JSON.stringify(item) === JSON.stringify(object)
);
},
filterEventsByOwner(owner) {
let filteredArray = [];
this.eventsData.forEach((item) => {
let foundEvent = item.employees.find(
(elem) => elem.employee.id === owner.id && elem.role === "owner"
);
if (foundEvent) filteredArray.push(item);
});
return filteredArray;
},
changeScrollingState(e) {
this.isScrolling = e.target.scrollTop !== 0;
},
showCuttentTime() {
this.$nextTick(() =>
this.$refs["scheduleBody"].scrollTo({
top: `${this.lineIndicatorLocation.top.slice(0, -2) - 240}`,
behavior: "smooth",
})
);
},
/*transmitResetChangeForm() {
this.$emit("reset-change-form");
},*/
transmitEventData(eventData) {
this.$emit("selected-event", eventData);
},
transmitDeleteEvent(eventData) {
this.$emit("delete-event", eventData);
},
bodyScroll() {
this.$nextTick(() => {
let presenceScroll =
this.$refs["scheduleBody"].scrollHeight !==
this.$refs["scheduleBody"].clientHeight;
if (presenceScroll) {
this.$refs["scheduleBody"].classList.add("pr-10px");
this.$refs["schedule"].classList.add("pr-10px");
}
});
},
},
watch: {
currentTime() {
if (
this.hours === this.validateEndTime &&
this.minutes > 0 &&
this.timer
) {
this.stopTimer();
this.timeCoilInitialization();
}
},
currentDate: function () {
this.isCurrentDate =
this.currentDate.format("DD.MM.YYYY") === moment().format("DD.MM.YYYY");
this.isShownIndicator = this.isCurrentDate;
if (this.timer) {
this.stopTimer();
this.timeCoilInitialization();
}
if (this.isCurrentDate) {
this.changeCurrentTime();
this.timeCoilInitialization();
this.startTimer();
this.showCuttentTime();
}
},
},
mounted() {
this.changeCurrentTime();
this.timeCoilInitialization();
this.startTimer();
this.showCuttentTime();
this.bodyScroll();
},
beforeUnmount() {
this.stopTimer();
},
};
</script>
<style lang="sass" scoped>
.schedule
border-top-left-radius: 4px
border-top-right-radius: 4px
position: relative
background-color: var(--default-white)
width: calc(100% - (var(--sidebar-width) + 16px))
height: calc(100vh - 56px - 8px)
.time-line-indicator
border-top: 1px solid var(--bg-event-red-color)
position: absolute
z-index: 4
.time-circle-indicator
width: 12px
height: 12px
background-color: var(--bg-event-red-color)
border-radius: 50%
position: absolute
z-index: 5
.column-wrapper
position: relative
height: 48px
background-color: var(--default-white)
.time-coil-wrapper
position: sticky
z-index: 5
background-color: var(--default-white)
.schedule-body
width: 100%
height: calc(100vh - 56px * 2 - 8px - 22px)
overflow-y: auto
overflow-x: auto
&::-webkit-scrollbar-track:horizontal
margin: 0 24px 0 104px
&::-webkit-scrollbar-track:vertical
margin: 48px 0 24px 0
.hiding-container
width: 80px
height: 48px
top: calc(56px * 2 + 8px)
background-color: var(--default-white)
z-index: 6
</style>

View File

@@ -1,113 +0,0 @@
<template lang="pug">
.sidebar.flex.flex-col.bg-white(:class="openSidebar")
.sidebar-wrapper.h-full.my-13px.flex.flex-col.justify-between(:style="sidebarWidth")
.sidebar-content.items-center.flex.flex-col.gap-y-8.px-4.py-19px
q-btn(
color="primary",
round,
icon="add",
size="13px",
@click="openFormCreate",
v-if="!isOpen"
)
q-btn(
no-caps
label="Создать событие",
color="primary",
class="text-weight-medium, full-width",
icon-right="add",
@click="openFormCreate",
v-else
)
calendar-sidebar-event(:is-open="isOpen", :event-statuses="eventStatuses")
calendar-sidebar-teammate(:team-data="teamData", :is-open="isOpen", :url="url")
//.button-wrapper.flex.justify-center.mb-23px
q-btn(
round,
icon="app:icon-long-arrow",
size="13px",
color="secondary",
text-color="primary"
:style="{ transform: `rotate(${turnButton})`}",
@click="changeSize",
)
</template>
<script>
import CalendarSidebarEvent from "./CalendarSidebarEvent.vue";
import CalendarSidebarTeammate from "./CalendarSidebarTeammate.vue";
export default {
name: "CalendarSidebar",
components: {
CalendarSidebarEvent,
CalendarSidebarTeammate,
},
props: {
schedulesData: Array,
openFormCreate: Function,
eventStatuses: Array,
url: String,
},
data() {
return {
widthSidebarOpen: "232px",
widthSidebarClose: "72px",
isOpen: false,
turnButton: "180deg",
};
},
computed: {
openSidebar() {
return {
"open-sidebar": this.isOpen,
};
},
sidebarWidth() {
if (this.isOpen) {
return {
width: this.widthSidebarOpen,
};
}
return {
width: this.widthSidebarClose,
};
},
teamData() {
if (this.schedulesData.length === 0) return [];
return this.schedulesData.map((elem) => elem.employee);
},
},
methods: {
changeSize() {
this.isOpen = !this.isOpen;
this.$emit(
"width",
this.isOpen ? this.widthSidebarOpen : this.widthSidebarClose
);
this.turnButton = this.isOpen ? "0deg" : "180deg";
},
},
};
</script>
<style lang="sass" scoped>
.sidebar
border-top-right-radius: 4px
.sidebar-wrapper
border-left: 2px solid var(--btn-blue-color-3)
.open-sidebar
.sidebar-content
display: flex
flex-direction: column
row-gap: 32px
padding: 19px 16px
align-items: center
width: 232px
.button-wrapper
justify-content: end
padding-right: 16px
</style>

View File

@@ -1,82 +0,0 @@
<template lang="pug">
.flex.flex-col.items-center.w-full
.flex.flex-col.items-center(v-if="!isOpen")
q-btn(
round,
icon="add",
size="12px",
text-color="primary",
color="secondary"
padding="4px",
dense
)
.flex.flex-col.gap-y-2.items-center.mt-4
.event.flex.items-center.justify-center(v-for="event in eventStatuses" :key="event.id")
.event-type(:style="{ background: event.color }")
.flex.flex-col.gap-y-4.w-full(v-else)
.flex.items-center.justify-between
.flex.text-base.font-bold(:style="{ color: 'var(--font-dark-blue-color)' }") Виды событий
q-btn(
v-if="isOpen",
round,
icon="add",
size="12px",
text-color="primary",
color="secondary"
padding="4px"
)
.flex.flex-col.gap-y-2
.relative.flex.items-center.gap-x-3.h-8(v-for="event in eventStatuses")
input.custom-input.py-2.pl-6.h-full.not-italic.font-medium.text-xxs(
:placeholder="event.label"
:key="event.id"
)
.event-open(:style="{ background: event.color }")
span.icon-edit.cursor-pointer
</template>
<script>
export default {
name: "CalendarSidebarEvent",
props: {
isOpen: Boolean,
eventStatuses: Array,
},
};
</script>
<style lang="sass" scoped>
.event
width: 32px
height: 32px
background: var(--btn-blue-sec-color)
border-radius: 4px
.event-type
width: 16px
height: 16px
border-radius: 2px
.custom-input
padding-top: 9px
padding-left: 24px
border: none
border-radius: 4px
width: 169px
outline: none
background-color: var(--btn-blue-sec-color)
&::placeholder
color: var(--font-dark-blue-color)
&:focus
background-color: var(--font-dark-blue-color)
color: var(--default-white)
&::placeholder
color: var(--default-white)
.event-open
position: absolute
width: 8px
height: 16px
top: 8px
left: 8px
</style>

View File

@@ -1,86 +0,0 @@
<template lang="pug">
.flex.flex-col.items-center.gap-y-2.justify-center(v-if="!isOpen")
q-btn(
round,
icon="add",
size="12px",
text-color="primary",
color="secondary"
padding="4px",
dense
)
.team-card(v-for="teammate in teamData" :key="teammate.id")
base-avatar(:size="32", :color="teammate.color")
img.h-full.object-cover(:src="url + teammate.photo", alt="Team member", v-if="teammate.photo")
span(v-if="!teammate.photo") {{teammateAvatar(teammate)}}
.flex.flex-col.gap-y-4.w-full(v-else, :style="{ color: 'var(--font-dark-blue-color)' }")
.flex.items-center.justify-between
.flex.text-base.font-bold Команды
q-btn(
round,
icon="add",
size="12px",
text-color="primary",
color="secondary"
padding="4px",
dense
)
.box-team.flex.flex-col.gap-y-2
.team-card.flex.items-center.justify-between.cursor-pointer(
v-for="teammate in teamData",
:key="teammate.id"
)
.flex.items-center
base-avatar(:size="32", :color="teammate.color")
img.h-full.object-cover(:src="url + teammate.photo", alt="Team member", v-if="teammate.photo")
span(v-if="!teammate.photo") {{teammateAvatar(teammate)}}
.flex.ml-2.not-italic.font-medium.text-xxs {{ changeName(teammate.last_name, teammate.first_name, teammate.patronymic) }}
span.icon-change-place.cursor-pointer.w-5.flex.items-center.justify-center.w-6.h-6
</template>
<script>
import BaseAvatar from "@/components/base/BaseAvatar";
export default {
name: "CalendarSidebarTeammate",
props: {
teamData: Array,
isOpen: Boolean,
url: String,
},
components: {
BaseAvatar,
},
data() {
return {
maxLengthLastName: 13,
};
},
methods: {
changeName(lastName, firstName, patronymic) {
let checkedFirstName = firstName !== null ? firstName[0] + "." : "";
let checkedPatronymic = patronymic !== null ? patronymic[0] + "." : "";
return lastName.length > this.maxLengthLastName
? lastName.slice(0, this.maxLengthLastName) +
"... " +
checkedFirstName +
checkedPatronymic
: lastName + " " + checkedFirstName + checkedPatronymic;
},
teammateAvatar(teammate) {
let checkedFirstName =
teammate.first_name !== null
? teammate.first_name[0]
: teammate.last_name[1];
return `${teammate.last_name[0]}${checkedFirstName}`;
},
},
};
</script>
<style lang="sass" scoped>
.team-card
border-radius: 16px
&:hover
background: var(--font-dark-blue-color)
color: var(--default-white)
</style>

View File

@@ -1,44 +0,0 @@
export const statusesConfig = [
{
id: 0,
label: "Планируется прием",
value: "PLANNED",
color: "var(--default-white)",
},
{
id: 1,
label: "Отменил",
value: "CANCELED",
color: "var(--bg-event-grey-color)",
},
{
id: 2,
label: "Ожидается прием",
value: "EXPECTED",
color: "var(--bg-event-yellow-color)",
},
{
id: 3,
label: "Опаздывает",
value: "LATE",
color: "var(--bg-event-orange-color)",
},
{
id: 4,
label: "Идет прием",
value: "RECEPTION",
color: "var(--bg-event-blue-color)",
},
{
id: 5,
label: "Прием завершен",
value: "COMPLETED",
color: "var(--bg-event-green-color)",
},
{
id: 6,
label: "Не пришел",
value: "DID_NOT_COME",
color: "var(--bg-event-red-color)",
},
];

View File

@@ -1,13 +0,0 @@
<template lang="pug">
medical-card-wrapper
</template>
<script>
import MedicalCardWrapper from "@/pages/oldMedicalCard/components/MedicalCardWrapper";
export default {
name: "TheMedicalCard",
components: { MedicalCardWrapper },
};
</script>
<style lang="sass" scoped></style>

View File

@@ -1,92 +0,0 @@
<template lang="pug">
.base.flex.flex-col.gap-y-6
.flex.flex-col.gap-y-6
span.font-bold Основное
.flex.flex-col.gap-y-6
base-input.w-full(
placeholder="ФИО*",
v-model="clientDetail.fullName",
disabled,
size="M"
)
base-radio-buttons-group(
:items="gendersList",
radioButtonsLabel="Пол",
v-model="clientDetail.gender",
inline
)
.flex.gap-x-4
.input
base-input(
type="date",
label="Дата рождения",
v-model="clientDetail.birth_date",
size="M"
)
.input
base-input(
placeholder="000000000 00",
mask="###-###-### ##",
label="СНИЛС",
v-model="clientDetail.insurance_number",
size="M"
)
.flex.flex-col.gap-y-6
base-input(
label="Адрес регистрации",
placeholder="Введите полный адрес",
v-model="clientDetail.registration_address"
size="M"
)
base-input(
label="Фактический адрес места жительства",
placeholder="Введите полный адрес",
v-model="clientDetail.temporary_address"
size="M"
)
.flex.gap-x-4
.input
base-input(
label="Номер телефона",
placeholder="+7 (915) 6449223",
mask="+7 (###) ###-##-##",
v-model="clientDetail.phone",
size="M"
)
.input
base-input(
label="Email",
placeholder="user@yandex.ru"
v-model="clientDetail.email",
size="M"
)
</template>
<script>
import BaseInput from "@/components/base/BaseInput";
import BaseSelect from "@/components/base/BaseSelect";
import BaseRadioButtonsGroup from "@/components/base/BaseRadioButtonsGroup.vue";
import { medicalDetailConfig } from "@/pages/oldMedicalCard/utils/medicalConfig.js";
export default {
name: "MedicalBaseData",
components: {
BaseInput,
BaseSelect,
BaseRadioButtonsGroup,
},
props: {
clientDetail: Object,
},
computed: {
gendersList() {
return medicalDetailConfig.gendersList;
},
},
};
</script>
<style lang="sass" scoped>
.input
width: 277px
</style>

View File

@@ -1,29 +0,0 @@
<template lang="pug">
.flex.w-full
.flex.w-full.text-lgx.font-bold.items-center.justify-center Медицинская карта {{numberCard}}
.flex.h-10.gap-x-2
q-btn(
outline,
icon="icon-download",
color="primary",
size="12px",
padding="8px 24px",
)
q-btn(
color="primary",
icon="add",
label="Создать",
style="width: 146px"
padding="6px 24px",
no-caps
)
</template>
<script>
export default {
name: "MedicalCardHeader",
props: {
numberCard: String,
},
};
</script>

View File

@@ -1,429 +0,0 @@
<template lang="pug">
.wrapper.flex.w-full.relative.mx-2
.wrapper-medical.relative.flex.gap-y-4.flex-col.h-full.w-full( class="pb-52px")
.card-upper.flex.flex-col.gap-y-4.px-6.py-6
medical-card-header(:number-card="numberCard")
.card-basic-info.flex
base-detail-info(
:title="configData.identity_document.title"
:height="configData.identity_document.height"
:data="defineEmptyData(configData.identity_document.fields)"
)
base-detail-input(
v-for="field in configData.identity_document.fields"
:field="field"
:data="medicalDataInit"
:mask="field.mask"
)
.flex.flex-col.gap-30px
base-detail-info(
:title="configData.registration_address.title"
:height="configData.registration_address.height"
:data="defineEmptyData(configData.registration_address.fields)"
)
base-detail-input(
v-for="field in configData.registration_address.fields"
:field="field"
:data="medicalDataInit"
)
base-detail-info(
:title="configData.temporary_address.title"
:height="configData.temporary_address.height"
:data="defineEmptyData(configData.temporary_address.fields)"
)
base-detail-input(
v-for="field in configData.temporary_address.fields"
:field="field"
:data="medicalDataInit"
)
.flex.flex-col.gap-2
base-detail-info(
:title="configData.snils.title"
:height="configData.snils.height"
:data="defineEmptyData(configData.snils.fields)"
)
base-detail-input(
v-for="field in configData.snils.fields"
:field="field"
:data="medicalDataInit"
:mask="field.mask"
)
base-detail-info(
:title="configData.policy.title"
:height="configData.policy.height"
:data="defineEmptyData(configData.policy.fields)"
)
base-detail-input(
v-for="field in configData.policy.fields"
:field="field"
:data="medicalDataInit"
:mask="field.mask"
)
base-detail-info(
:title="configData.policy_organization.title"
:height="configData.policy_organization.height"
:data="defineEmptyData(configData.policy_organization.fields)"
)
base-detail-input(
v-for="field in configData.policy_organization.fields"
:field="field"
:data="medicalDataInit"
)
.flex.flex-col.gap-4
base-detail-info(
:title="configData.additional.title"
:height="configData.additional.height"
:data="defineEmptyData(configData.additional.fields)"
)
base-detail-input(
v-for="field in configData.additional.fields"
:field="field"
:data="medicalDataInit"
:mask="field.mask"
)
base-detail-info(
:title="configData.agreement_form.title"
:height="configData.agreement_form.height"
:data="defineEmptyData(configData.agreement_form.fields)"
direction-row
)
base-detail-input(
v-for="field in configData.agreement_form.fields"
:field="field"
:data="medicalDataInit"
)
.flex.flex-col(class="px-92px gap-y-60px")
.flex.flex-col(class="gap-y-2.5")
.flex.flex-col.gap-y-2(v-for="(key, index) in Object.keys(dentalIndications)")
span.font-bold.text-xsx {{`${index+1}. ${configData.dental_indications[key]}`}}
medical-dental-formula(v-if="key === 'dental_formula'" :formula-data="dentalIndications[key]")
base-input(v-model="dentalIndications[key]", v-else, size="M")
.flex.justify-center
q-btn(
@click="validateFormCard",
color="primary",
label="Сохранить",
padding="8px 24px",
no-caps
)
</template>
<script>
import MedicalCardHeader from "@/pages/oldMedicalCard/components/MedicalCardHeader.vue";
import MedicalDentalFormula from "@/pages/oldMedicalCard/components/MedicalDentalFormula.vue";
import BaseDetailInput from "@/components/base/BaseDetailInput.vue";
import BaseDetailInfo from "@/components/base/BaseDetailInfo.vue";
import { formulaDataMap } from "@/pages/oldMedicalCard/utils/medicalConfig";
import { medicalDetailConfig } from "@/pages/oldMedicalCard/utils/medicalConfig";
import { mobilityMap } from "@/pages/oldMedicalCard/utils/medicalConfig";
import {
getDentalEntiesValue,
requestUpdateMedicalCard,
requestCreateMedicalCard,
getRequestDataPerson,
} from "@/pages/oldMedicalCard/utils/wrapperMedicalCard";
import { fetchWrapper } from "@/shared/fetchWrapper";
import BaseInput from "@/components/base/BaseInput.vue";
import ClientDetailInput from "@/pages/clients/components/ClientDetailInput.vue";
export default {
name: "MedicalCardWrapper",
components: {
ClientDetailInput,
BaseInput,
BaseDetailInfo,
BaseDetailInput,
MedicalCardHeader,
MedicalDentalFormula,
},
data() {
return {
personId: "",
identityDocumentId: "",
responseMedicalCardData: undefined,
responseMedicalDentalData: {},
numberCard: "",
configData: medicalDetailConfig,
medicalDataInit: {
number_series: undefined,
issued_by_org: undefined,
issued_by_org_code: undefined,
issued_by_date: undefined,
registration_address: undefined,
temporary_address: undefined,
insurance_number: undefined,
policy_series: undefined,
policy_number: undefined,
policy_organization: undefined,
name_confidant: undefined,
phone_confidant: undefined,
benefit_code: undefined,
agreement: undefined,
agreement_date: undefined,
},
dentalIndications: {
complaints: undefined,
allergic: undefined,
past_diseases: undefined,
medications_taken: undefined,
thermometry: undefined,
tonometry: undefined,
disease_progress: undefined,
visual_examination: undefined,
oral_examination: undefined,
dental_formula: formulaDataMap,
dental_bite: undefined,
hygiene_index: undefined,
dmf_index: undefined,
diagnostic_findings: undefined,
icd_code: undefined,
survey_plan: undefined,
survey_data: undefined,
clinical_diagnosis: undefined,
treatment_plan: undefined,
treatment_protocol: undefined,
},
};
},
methods: {
defineEmptyData(fields) {
return fields.map((field) => this.medicalDataInit[field.label]);
},
validateFormCard() {
const dataRequestPerson = getRequestDataPerson(
this.medicalDataInit,
this.dentalIndications
);
Object.keys(dataRequestPerson).forEach((key) => {
let valuesData = [
...new Set(
Object.values(this.responseMedicalCardData[key]).map((el) =>
el === null || el === undefined ? undefined : el
)
),
];
let isUpdateData = false;
let isCreateData =
valuesData.length === 1 &&
valuesData[0] === undefined &&
Object.values(dataRequestPerson[key]).find((el) => el !== undefined);
Object.keys(dataRequestPerson[key]).forEach((entries) => {
if (
this.responseMedicalCardData[key][entries] &&
dataRequestPerson[key][entries] !==
this.responseMedicalCardData[key][entries]
) {
isUpdateData = true;
}
});
if (isUpdateData) {
requestUpdateMedicalCard(
dataRequestPerson[key],
key,
key === "identity_document"
? this.identityDocumentId
: this.personId
);
}
if (isCreateData) {
requestCreateMedicalCard(
dataRequestPerson[key],
key,
key === "identity_document"
? this.identityDocumentId
: this.personId
);
}
});
Object.keys(this.dentalIndications).forEach((key) => {
if (key === "dental_formula") {
const notEmptyEntries =
this.dentalIndications.dental_formula[0].columns
.concat(this.dentalIndications.dental_formula[1].columns)
.filter((el) => el.dental_condition && el.tooth_mobility);
notEmptyEntries.forEach((tooth) => {
const toothResponse = this.responseMedicalDentalData[key].find(
(el) => el.tooth_number === tooth.tooth_number
);
const isCreate =
!toothResponse &&
!!tooth.tooth_mobility &&
!!tooth.dental_condition;
if (isCreate) {
requestCreateMedicalCard(tooth, key, this.$route.params.id);
}
if (
toothResponse &&
toothResponse.tooth_mobility.label !==
tooth.tooth_mobility.label &&
toothResponse.dental_condition !== tooth.dental_condition
) {
requestUpdateMedicalCard(tooth, key, toothResponse.id);
}
});
}
if (
key !== "dental_formula" &&
!this.responseMedicalDentalData[key] &&
this.dentalIndications[key]
) {
requestCreateMedicalCard(
this.dentalIndications[key],
key,
this.$route.params.id
);
}
if (
key !== "dental_formula" &&
this.dentalIndications[key] &&
getDentalEntiesValue(this.responseMedicalDentalData[key], key) !==
this.dentalIndications[key]
) {
requestUpdateMedicalCard(
this.dentalIndications[key],
key,
this.responseMedicalDentalData[key]?.id
);
}
});
},
saveDataMedical(data) {
this.personId = data?.person?.id;
this.numberCard = data?.number;
const confidant = data ? data["confidant"] : undefined;
const pass = data
? data?.person?.identity_document?.find((el) => el.kind === "PASSPORT")
: null;
this.identityDocumentId = pass?.id;
this.medicalDataInit = {
...this.medicalDataInit,
issued_by_org: pass.issued_by_org,
issued_by_org_code: pass.issued_by_org_code,
number_series: `${pass.series} ${pass.number}`,
issued_by_date: new Date(pass.issued_by_date),
registration_address: data?.person?.address?.find(
(el) => el["registration_flg"]
)?.join_address,
temporary_address: data?.person?.address?.find(
(el) => el["temporary_registration_flg"]
)?.join_address,
insurance_number: data?.person["insurance_number"] || undefined,
policy_number: data?.person["insurance_policy"][0]?.number,
policy_series: data?.person["insurance_policy"][0]?.series,
policy_organization: data?.person["insurance_policy"][0]?.organization,
name_confidant:
confidant &&
`${confidant?.last_name} ${confidant?.last_name} ${confidant?.patronymic}`,
phone_confidant: confidant?.contacts?.find((el) => el?.kind === "PHONE")
?.username,
benefit_code: data?.person?.benefit_code,
agreement: data?.agreement,
agreement_date: undefined,
};
Object.keys(this.dentalIndications).forEach((key) => {
if (key === "dental_formula" && data[key].length) {
this.responseMedicalDentalData[key] = data[key];
data[key].forEach((tooth) => {
if (tooth.dental_condition && tooth.tooth_mobility) {
if (17 < tooth.tooth_number < 29) {
this.dentalIndications.dental_formula[0] = {
...this.dentalIndications.dental_formula[0],
columns: this.dentalIndications.dental_formula[0].columns.map(
(el) => {
if (el.tooth_number === tooth.tooth_number) {
return {
...tooth,
tooth_mobility: mobilityMap.find(
(el) => el.label === tooth.tooth_mobility
),
};
}
return el;
}
),
};
}
if (37 < tooth.tooth_number < 49) {
this.dentalIndications.dental_formula[1] = {
...this.dentalIndications.dental_formula[1],
columns: this.dentalIndications.dental_formula[1].columns.map(
(el) => {
if (el.tooth_number === tooth.tooth_number) {
return {
...tooth,
tooth_mobility: mobilityMap.find(
(el) => el.label === tooth.tooth_mobility
),
};
}
return el;
}
),
};
}
}
});
} else {
this.dentalIndications[key] = data[key]?.length
? getDentalEntiesValue(data[key].at(-1), key)
: this.dentalIndications[key];
this.dentalIndications.allergic = data?.person?.allergic
? data?.person?.allergic
: this.dentalIndications.allergic;
this.responseMedicalDentalData[key] = data[key]?.length
? data[key].at(-1)
: this.dentalIndications[key];
}
});
this.responseMedicalCardData = getRequestDataPerson(
this.medicalDataInit,
this.dentalIndications
);
},
async fetchDataMedicalCard() {
await fetchWrapper
.get(`medical_card/medical_history/${this.$route.params.id}/detail/`)
.then((res) => this.saveDataMedical(res));
},
},
mounted() {
this.fetchDataMedicalCard();
},
};
</script>
<style lang="sass" scoped>
.wrapper
overflow: auto
border-top-left-radius: 4px
border-top-right-radius: 4px
.wrapper-medical
background-color: var(--default-white)
height: calc(100vh - 64px)
overflow: auto
&::-webkit-scrollbar
height: 4px
width: 4px
&::-webkit-scrollbar-track
background-color: rgba(211, 212, 220, 0.5)
border-radius: 8px
&::-webkit-scrollbar-thumb
border-radius: 8px
background-color: var(--btn-blue-color)
.card-basic-info
display: grid
grid-template-columns: 280px 292px 180px 360px 463px
gap: 16px
padding: 0 20px 20px 0
overflow-x: auto
&::-webkit-scrollbar
height: 4px
width: 4px
&::-webkit-scrollbar-track
background-color: rgba(211, 212, 220, 0.5)
border-radius: 8px
&::-webkit-scrollbar-thumb
border-radius: 8px
background-color: var(--btn-blue-color)
.card-upper
border-bottom: 1px solid var(--border-light-grey-color)
</style>

View File

@@ -1,50 +0,0 @@
<template lang="pug">
.wrapper.flex.w-full.px-52px.py-7.gap-x-102px
.flex.flex-col.gap-y-5
.flex.flex-col.gap-y-4
span.font-bold.text-base Состояние зубов:
.flex.flex-col.gap-y-1
div(v-for="condition in dentalCondition")
span.font-bold.text-base {{condition.name}}
span.text-base {{` - ${condition.discription}`}}
.flex.flex-col.gap-y-1
.font-bold.text-base Подвижность:
span.text-base Степень -
span.font-bold.text-base I, II, III
.flex.flex-col.h-fit.rounded
medical-formula-table(:data="formulaData[0]")
medical-formula-table(:data="formulaData[1]")
</template>
<script>
import { dentalСonditionMap } from "@/pages/oldMedicalCard/utils/medicalConfig";
import MedicalFormulaTable from "@/pages/oldMedicalCard/components/MedicalFormulaTable.vue";
export default {
name: "MedicalDentalFormula",
components: { MedicalFormulaTable },
props: {
formulaData: Array,
},
data() {
return {
dentalCondition: dentalСonditionMap,
};
},
};
</script>
<style lang="sass" scoped>
.wrapper
border: 1.5px solid var(--border-light-grey-color)
border-radius: 4px
overflow: auto
&::-webkit-scrollbar
height: 4px
width: 4px
&::-webkit-scrollbar-track
background-color: rgba(211, 212, 220, 0.5)
border-radius: 8px
&::-webkit-scrollbar-thumb
border-radius: 8px
background-color: var(--btn-blue-color)
</style>

View File

@@ -1,48 +0,0 @@
<template lang="pug">
//TODO: Поправить бордеры у таблицы
.table-formula.flex.border-collapse
.flex.flex-col.border-collapse
.cell-grey.flex.w-52.h-50px.font-bold.text-lg.justify-center.items-center.border-collapse(v-for="row in data.rowMap") {{row.label}}
.flex.flex-col.border-collapse(v-for="tooth in data.columns")
.cell.flex.w-50px.h-50px.font-bold.text-2xl.justify-center.items-center(v-for="(row, index) in data.rowMap" :class="{'cell-grey': row.name === 'tooth_number'}")
span(v-if="row.name === 'tooth_number'") {{tooth[row.name]}}
base-input.pl-4.pr-2(v-if="row.name === 'dental_condition'" v-model="tooth[row.name]" :rules="[ruleCondition]" :max-length="2" borderless no-error-icon)
label.flex.w-full.h-full.justify-center.items-center.cursor-pointer(v-if="row.name === 'tooth_mobility'")
base-select.pl-4.pr-4(v-model="tooth[row.name]" :id="`${row.name}_${index}`" :items="mobilityMap" :outlined="false" menu-anchor="bottom start" borderless hide-dropdown-icon)
</template>
<script>
import { ruleDentalCondition } from "@/assets/rules/rulesInput";
import BaseInput from "@/components/base/BaseInput.vue";
import BaseSelect from "@/components/base/BaseSelect.vue";
import { mobilityMap } from "@/pages/oldMedicalCard/utils/medicalConfig";
export default {
name: "MedicalFormulaTable",
components: { BaseInput, BaseSelect },
props: {
data: Object,
rowMap: Array,
condition: Array,
},
data() {
return {
ruleCondition: ruleDentalCondition,
mobilityMap: mobilityMap,
};
},
};
</script>
<style lang="sass" scoped>
.table-formula
border-left: 1.5px solid var(--font-black-color)
border-top: 1.5px solid var(--font-black-color)
.cell
border-bottom: 1.5px solid black
border-right: 1.5px solid black
.cell-grey
@extend .cell
background-color: var(--border-light-grey-color)
.not-border
border: none
</style>

View File

@@ -1,47 +0,0 @@
<template lang="pug">
.base.flex.flex-col.gap-y-6
.flex.flex-col.gap-y-6
span.font-bold Паспортные данные
.flex.flex-col.gap-y-6
base-input(
label="Серия и номер",
placeholder="0000 000000",
mask="#### ######",
v-model="clientDetail.identity_documents.series_number",
size="M"
)
base-input(
label="Кем выдан",
placeholder="Точно как в паспорте",
v-model="clientDetail.identity_documents.issued_by_org",
size="M"
)
.flex.gap-x-4
base-input(
label="Код подразделения",
placeholder="000-000",
mask="###-###",
v-model="clientDetail.identity_documents.issued_by_org_code",
size="M"
)
base-input(
type="date",
label="Дата выдачи",
v-model="clientDetail.identity_documents.issued_by_date",
size="M"
)
</template>
<script>
import BaseInput from "@/components/base/BaseInput";
import BaseSelect from "@/components/base/BaseSelect";
export default {
name: "MedicalIdentityDocuments",
components: { BaseInput, BaseSelect },
props: {
changeIdentityDoc: Function,
clientDetail: Object,
},
};
</script>

View File

@@ -1,93 +0,0 @@
<template lang="pug">
.base.flex.flex-col.gap-y-6
span.font-bold Полис
base-radio-buttons-group(
:items="policiesList",
v-model="clientDetail.policy",
:columnGap="152",
inline
)
.flex.gap-x-4
.flex.flex-col.gap-y-4
base-input.input(
label="Серия и номер полиса ОМС",
placeholder="000000 0000000000",
v-model="clientDetail.OMSPolicy.number",
mask="###### ##########",
:disabled="!selectedOMSPolicy",
size="M"
)
base-input.input(
label="Страховая организация полиса ОМС",
v-model="clientDetail.OMSPolicy.organization",
:disabled="!selectedOMSPolicy",
size="M"
)
.flex.flex-col.gap-y-4
base-input.input(
label="Серия и номер полиса ДМС",
placeholder="000000 0000000000",
v-model="clientDetail.DMSPolicy.number",
mask="###### ##########",
:disabled="selectedOMSPolicy",
size="M"
)
base-input.input(
label="Страховая организация полиса ДМС",
v-model="clientDetail.DMSPolicy.organization",
:disabled="selectedOMSPolicy",
size="M"
)
base-input(
label="Код категории льготы",
placeholder="000",
mask="###"
v-model="clientDetail.benefit_code",
size="M"
)
base-input(
label="К кому обращаться в случае необходимости",
placeholder="ФИО*",
v-model="clientDetail.confidant_name",
size="M"
)
base-input(
label="Номер телефона",
placeholder="+7 (915) 6449223",
mask="+7 (###) ###-##-##",
v-model="clientDetail.confidant_phone",
size="M"
)
</template>
<script>
import BaseInput from "@/components/base/BaseInput";
import BaseSelect from "@/components/base/BaseSelect";
import BaseRadioButtonsGroup from "@/components/base/BaseRadioButtonsGroup.vue";
import { medicalDetailConfig } from "@/pages/oldMedicalCard/utils/medicalConfig.js";
export default {
name: "MedicalPolicyDocuments",
components: {
BaseInput,
BaseSelect,
BaseRadioButtonsGroup,
},
props: {
clientDetail: Object,
},
computed: {
policiesList() {
return medicalDetailConfig.policiesList;
},
selectedOMSPolicy() {
return this.clientDetail.policy === "OMS";
},
},
};
</script>
<style lang="sass" scoped>
.input
width: 277px
</style>

View File

@@ -1,449 +0,0 @@
export const medicalDetailConfig = {
gendersList: [
{
id: "1",
label: "Мужской",
value: "MALE",
},
{
id: "2",
label: "Женский",
value: "WOMEN",
},
],
policiesList: [
{
id: "1",
label: "Полис ОМС",
value: "OMS",
},
{
id: "2",
label: "Полис ДМС",
value: "DMS",
},
],
identity_document: {
height: 316,
title: "Паспортные данные",
fields: [
{
label: "number_series",
title: "Серия и номер",
type: "text",
mask: "#### ######",
copy: true,
},
{
label: "issued_by_org",
title: "Выдан",
type: "textarea",
},
{
label: "issued_by_org_code",
title: "Код подразделения",
type: "text",
mask: "###-###",
},
{
label: "issued_by_date",
title: "Дата выдачи",
type: "date",
},
],
},
registration_address: {
height: 85,
title: "Адрес регистрации",
fields: [
{
label: "registration_address",
title: "Полный адрес",
type: "textarea",
},
],
},
temporary_address: {
height: 85,
title: "Адрес проживания",
fields: [
{
label: "temporary_address",
title: "Полный адрес",
type: "textarea",
},
],
},
snils: {
height: 85,
title: "СНИЛС",
fields: [
{
label: "insurance_number",
title: "Номер",
type: "text",
mask: "###-###-### ##",
copy: true,
},
],
},
policy: {
height: 162,
title: "ПОЛИС",
fields: [
{
label: "policy_series",
title: "Серия",
type: "text",
mask: "####",
},
{
label: "policy_number",
title: "Номер",
type: "text",
mask: "#######",
},
],
},
policy_organization: {
height: 85,
title: "Страховая организация",
fields: [
{
label: "policy_organization",
title: "Название",
type: "text",
},
],
},
additional: {
height: 218,
title: "Дополнительная информация",
fields: [
{
icon: "icon-person",
label: "name_confidant",
title: "Доверенное лицо",
type: "text",
},
{
icon: "icon-phone",
label: "phone_confidant",
title: "",
type: "text",
mask: "+7 (###) ###-##-##",
},
{
label: "benefit_code",
title: "Код категории льготы",
type: "text",
mask: "###",
},
],
},
agreement_form: {
height: 121,
title:
"Информированное добровольное согласие на виды медицинских вмешательств, включенные в Перечень определенных видов медицинских вмешательств:",
fields: [
{
label: "agreement",
title: "Получено",
type: "radio",
items: [
{
id: "1",
label: "Да",
value: true,
},
{
id: "2",
label: "Нет",
value: false,
},
],
},
{
label: "agreement_date",
title: "Дата",
type: "date",
},
],
},
dental_indications: {
complaints: "Жалобы",
allergic: "Аллергологический анамнез",
past_diseases: "Перенесённые и сопутствующие заболевания",
medications_taken: "Принимаемые лекарственные препараты",
thermometry: "Термометрия (по показаниям)",
tonometry: "Тонометрия, ЧСС (по показаниям)",
disease_progress: "Развитие настоящего заболевания",
visual_examination: "Внешний осмотр",
oral_examination: "Осмотр преддверия и полости рта",
dental_formula: "Зубная формула",
dental_bite: "Прикус",
hygiene_index: "Индекс гигиены",
dmf_index: "Индекс КПУ",
diagnostic_findings: "Предварительный диагноз",
icd_code: "Код МКБ-10",
survey_plan: "План обследования",
survey_data: "Данные обследования",
clinical_diagnosis: "Клинический диагноз",
treatment_plan: "План лечения",
treatment_protocol: "Протокол лечения",
},
};
export const dentalСonditionMap = [
{
name: "К",
discription: "кариес",
},
{
name: "П",
discription: "пульпит",
},
{
name: "Пт",
discription: "периодонтит",
},
{
name: "Д",
discription: "дефект",
},
{
name: "Пл",
discription: "пломба",
},
{
name: "Ик",
discription: "искусственная коронка",
},
{
name: "Из",
discription: "искусственный зуб",
},
{
name: "В",
discription: "вкладка",
},
{
name: "И",
discription: "имплантат",
},
{
name: "Кз",
discription: "корень зуба",
},
{
name: "О",
discription: "отсутствующий зуб",
},
];
export const mobilityMap = [
{ id: 1, label: "I" },
{ id: 2, label: "II" },
{ id: 3, label: "III" },
];
export const formulaDataMap = [
{
name: "upperJaw",
rowMap: [
{
label: "Подвижность",
name: "tooth_mobility",
},
{
label: "Cостояние зубов",
name: "dental_condition",
},
{
label: "Верхняя челюсть",
name: "tooth_number",
},
],
columns: [
{
tooth_number: 18,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 17,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 16,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 15,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 14,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 13,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 12,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 11,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 21,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 22,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 23,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 24,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 25,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 26,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 27,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 28,
dental_condition: "",
tooth_mobility: "",
},
],
},
{
name: "lowerJaw",
rowMap: [
{
label: "Нижняя челюсть",
name: "tooth_number",
},
{
label: "Cостояние зубов",
name: "dental_condition",
},
{
label: "Подвижность",
name: "tooth_mobility",
},
],
columns: [
{
tooth_number: 48,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 47,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 46,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 45,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 44,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 43,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 42,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 41,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 31,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 32,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 33,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 34,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 35,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 36,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 37,
dental_condition: "",
tooth_mobility: "",
},
{
tooth_number: 38,
dental_condition: "",
tooth_mobility: "",
},
],
},
];

View File

@@ -1,226 +0,0 @@
import { fetchWrapper } from "@/shared/fetchWrapper";
import moment from "moment";
export function requestCreateMedicalCard(entry, key, id) {
const url = `medical_card/${key}/create/`;
let arrayValue;
if (["tonometry", "dental_bite", "dmf_index"].includes(key)) {
arrayValue = entry.split(",");
}
switch (key) {
case "complaints":
case "medications_taken":
case "disease_progress":
case "visual_examination":
case "diagnostic_findings":
case "survey_plan":
case "treatment_plan":
case "treatment_protocol":
return fetchWrapper.post(url, {
title: entry,
description: entry,
medical_history: id,
});
case "thermometry":
return fetchWrapper.post(url, {
title: entry,
temperature: Number(entry),
medical_history: id,
});
case "tonometry":
return fetchWrapper.post(url, {
title: entry,
sys: Number(arrayValue[0]),
dia: Number(arrayValue[1]),
pul: Number(arrayValue[2]),
medical_history: id,
});
case "dental_bite":
return fetchWrapper.post(url, {
custom: arrayValue[0],
physiological: arrayValue[1],
abnormal: arrayValue[2],
medical_history: id,
});
case "dmf_index":
return fetchWrapper.post(url, {
caries: Number(arrayValue[0]),
filling: Number(arrayValue[1]),
remote: Number(arrayValue[2]),
medical_history: id,
});
case "clinical_diagnosis":
return fetchWrapper.post(url, {
title: entry,
description: "string",
medical_history: id,
icd10: id,
});
case "dental_formula":
return fetchWrapper.post(url, {
...entry,
tooth_mobility: entry.tooth_mobility ? entry.tooth_mobility.label : "",
tooth_number: entry.tooth_number,
medical_history: id,
});
case "registration_address":
return fetchWrapper.post(`general/address/create/`, {
...entry,
person: id,
registration_flg: true,
});
case "temporary_address":
return fetchWrapper.post(`general/address/create/`, {
...entry,
person: id,
temporary_registration_flg: true,
});
case "identity_document":
return fetchWrapper.post(`general/identity_document/create/`, {
...entry,
person: id,
});
case "insurance_policy":
return fetchWrapper.post(`general/insurance_policy/create/`, {
...entry,
title: "string",
person: id,
});
}
}
export function requestUpdateMedicalCard(entry, key, id) {
const url = `medical_card/${key}/${id}/update/`;
let arrayValue;
if (["tonometry", "dental_bite", "dmf_index"].includes(key)) {
arrayValue = entry.split(",");
}
switch (key) {
case "complaints":
case "medications_taken":
case "disease_progress":
case "visual_examination":
case "diagnostic_findings":
case "survey_plan":
case "treatment_plan":
case "treatment_protocol":
return fetchWrapper.post(url, {
title: entry,
description: entry,
});
case "thermometry":
return fetchWrapper.post(url, {
title: entry,
temperature: Number(entry),
});
case "tonometry":
return fetchWrapper.post(url, {
title: entry,
sys: Number(arrayValue[0]),
dia: Number(arrayValue[1]),
pul: Number(arrayValue[2]),
});
case "dental_bite":
return fetchWrapper.post(url, {
custom: entry,
physiological: entry,
abnormal: entry,
});
case "dmf_index":
return fetchWrapper.post(url, {
caries: Number(entry),
filling: Number(entry),
remote: Number(entry),
});
case "clinical_diagnosis":
return fetchWrapper.post(url, {
title: entry,
description: "string",
icd10: id,
});
case "dental_formula":
return fetchWrapper.post(url, {
...entry,
tooth_mobility: entry.tooth_mobility ? entry.tooth_mobility.label : "",
tooth_number: entry.tooth_number,
});
case "registration_address":
return fetchWrapper.post(`general/address/${id}/update/`, {
...entry,
registration_flg: true,
});
case "temporary_address":
return fetchWrapper.post(`general/address/${id}/update/`, {
...entry,
temporary_registration_flg: true,
});
case "identity_document":
return fetchWrapper.post(`general/identity_document/${id}/update/`, {
...entry,
});
case "insurance_policy":
return fetchWrapper.post(`general/insurance_policy/${id}/update/`, {
...entry,
title: "string",
});
case "person":
return fetchWrapper.post(`general/person/${id}/update/`, {
...entry,
});
}
}
export function getRequestDataPerson(person, dental) {
const dataPersonRequest = {
identity_document: {
issued_by_org: person.issued_by_org,
issued_by_org_code: person.issued_by_org_code,
issued_by_date: moment(person.issued_by_date).format("YYYY-MM-DD"),
number: person.number_series.split(" ")[1],
series: person.number_series.split(" ")[0],
},
registration_address: {
full_address: person.registration_address,
},
temporary_address: {
full_address: person.temporary_address,
},
insurance_policy: {
series: person.policy_series,
number: person.policy_number,
organization: person.policy_organization,
},
person: {
insurance_number: person.insurance_number,
benefit_code: person.benefit_code,
allergic: dental.allergic,
},
agreement: {
agreement: person.agreement,
},
};
return dataPersonRequest;
}
export function getDentalEntiesValue(data, key) {
switch (key) {
case "complaints":
case "medications_taken":
case "disease_progress":
case "visual_examination":
case "diagnostic_findings":
case "survey_plan":
case "treatment_plan":
case "treatment_protocol":
case "clinical_diagnosis":
return data?.description;
case "thermometry":
return `${data?.temperature}`;
case "tonometry":
return `${data?.sys},${data?.dia},${data?.pul}`;
case "dental_bite":
return `${data?.custom},${data?.physiological},${data?.abnormal}`;
case "dmf_index":
return data["index_calculation"];
case "hygiene_index":
return data["index_calculation"];
}
}

View File

@@ -1,12 +1,12 @@
import { createRouter, createWebHashHistory } from "vue-router"; import { createRouter, createWebHashHistory } from "vue-router";
import TheCalendar from "@/pages/newCalendar/TheCalendar"; import TheCalendar from "@/pages/newCalendar/TheCalendar";
import TheUser from "@/pages/clients/TheClients";
import TheSettings from "@/pages/settings/TheSettings"; import TheSettings from "@/pages/settings/TheSettings";
import TheMedcards from "@/pages/medcards/TheMedcards"; import TheMedcards from "@/pages/medcards/TheMedcards";
import TheLogin from "@/pages/login/TheLogin"; import TheLogin from "@/pages/login/TheLogin";
import LoggedInLayout from "@/components/LoggedInLayout"; import LoggedInLayout from "@/components/LoggedInLayout";
import TheMedicalCard from "@/pages/newMedicalCard/TheMedicalCard"; import TheMedicalCard from "@/pages/newMedicalCard/TheMedicalCard";
import { fetchWrapper } from "@/shared/fetchWrapper"; import { fetchWrapper } from "@/shared/fetchWrapper";
import TheHome from "@/pages/home/TheHome";
const ifNotAuthenticated = async (to, from, next) => { const ifNotAuthenticated = async (to, from, next) => {
const data = await fetchWrapper.get("users/me"); const data = await fetchWrapper.get("users/me");
@@ -35,9 +35,9 @@ export default createRouter({
beforeEnter: ifAuthenticated, beforeEnter: ifAuthenticated,
children: [ children: [
{ path: "", redirect: "calendar" }, { path: "", redirect: "home" },
{ path: "home", component: TheHome },
{ path: "calendar", component: TheCalendar }, { path: "calendar", component: TheCalendar },
{ path: "clients", component: TheUser },
{ path: "medcards", component: TheMedcards }, { path: "medcards", component: TheMedcards },
{ path: "settings", component: TheSettings }, { path: "settings", component: TheSettings },
{ path: "medical-card/:id", component: TheMedicalCard }, { path: "medical-card/:id", component: TheMedicalCard },

View File

@@ -1,6 +1,11 @@
const getOrigin = () =>
process.env.NODE_ENV === "development"
? "https://astra-dev.dopcore.com"
: window.origin;
function prepareUrl(url) { function prepareUrl(url) {
if (url.startsWith("http")) return url; if (url.startsWith("http")) return url;
return `https://astra-dev.dopcore.com/api/${url}`; return `${getOrigin()}/api/${url}`;
} }
function handleRequest(method, url, headers, attempts, body, type) { function handleRequest(method, url, headers, attempts, body, type) {
@@ -45,7 +50,7 @@ function request(method, url, headers = {}, body, type = "") {
requestOptions.body = JSON.stringify(body); requestOptions.body = JSON.stringify(body);
} }
} }
if (method === "POST" || method === "PUT") { if (method === "POST" || method === "PUT" || method === "PATCH") {
if (type && type === "formData") { if (type && type === "formData") {
requestOptions = { requestOptions = {
method: method, method: method,
@@ -83,6 +88,10 @@ function post(url, body, type, headers, attempts = 3) {
return handleRequest("POST", url, headers, attempts, body, type); return handleRequest("POST", url, headers, attempts, body, type);
} }
function patch(url, body, type, headers, attempts = 3) {
return handleRequest("PATCH", url, headers, attempts, body, type);
}
function put(url, headers, body, attempts = 3) { function put(url, headers, body, attempts = 3) {
return handleRequest("PUT", url, headers, attempts, null, body); return handleRequest("PUT", url, headers, attempts, null, body);
} }
@@ -92,4 +101,5 @@ export const fetchWrapper = {
del, del,
post, post,
put, put,
patch,
}; };

View File

@@ -20,10 +20,6 @@ export function getObjectChangeField(objInit, objUpdate) {
return resultObj; return resultObj;
} }
export function checkTypeofObject(obj) {
return typeof obj === "object" && obj !== "null";
}
export function checkChangeData(dataInit, dataChange) { export function checkChangeData(dataInit, dataChange) {
return JSON.stringify(dataInit) !== JSON.stringify(dataChange); return JSON.stringify(dataInit) !== JSON.stringify(dataChange);
} }

Some files were not shown because too many files have changed in this diff Show More