init
This commit is contained in:
18
src/entities/entities.scss
Normal file
18
src/entities/entities.scss
Normal file
@ -0,0 +1,18 @@
|
||||
/*---------------- User Styles ------------------------*/
|
||||
@import 'user/ui/UserAvatar/UserAvatar';
|
||||
|
||||
/*---------------- Patient Styles ---------------------*/
|
||||
@import 'patient/ui/PatientRequest/PatientRequest';
|
||||
@import 'patient/ui/ProgressBar/ProgressBar';
|
||||
@import 'patient/ui/PatientSurveyCard/PatientSurveyCard';
|
||||
@import 'patient/ui/PatientHealthMatrix/PatientHealthMatrix';
|
||||
@import 'patient/ui/PatientBasicInfo/PatientBasicInfo';
|
||||
@import 'patient/ui/PatientFilesCard/PatientFilesCard';
|
||||
@import 'patient/ui/PatientReminders/PatientReminders';
|
||||
@import 'patient/ui/EditableCard/EditableCard';
|
||||
@import 'patient/ui/InitialAppointment/InitialAppointment';
|
||||
@import 'patient/ui/QuestionnaireCard/QuestionnaireCard';
|
||||
@import 'patient/ui/EmptySurvey/EmptySurvey';
|
||||
@import 'patient/ui/InitialHealthMatrix/InitialHealthMatrix';
|
||||
@import 'patient/ui/InitialPurpose/InitialPurpose';
|
||||
@import 'patient/ui/EditableInput/EditableInput';
|
3
src/entities/index.ts
Normal file
3
src/entities/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './user'
|
||||
export * from './patient'
|
||||
export * from './medical'
|
245
src/entities/medical/api/index.ts
Normal file
245
src/entities/medical/api/index.ts
Normal file
@ -0,0 +1,245 @@
|
||||
import type { AxiosPromise } from 'axios'
|
||||
import type {
|
||||
Patient,
|
||||
PatientReminder,
|
||||
THealthMatrix,
|
||||
HealthMatrixData,
|
||||
PatientAnalysis,
|
||||
PatientTreatmentCourse,
|
||||
} from '@/entities'
|
||||
import { medicalApi } from '@/shared'
|
||||
import type {
|
||||
Medical,
|
||||
SetSurveyData,
|
||||
AddReminderData,
|
||||
TreatmentCourse,
|
||||
AddTreatmentCourseData,
|
||||
DeleteAppointmentData,
|
||||
DeleteTreatmentCourseFromPatientData,
|
||||
MedicalSurvey,
|
||||
ViewSurveyAnswersData,
|
||||
SurveyDetail,
|
||||
MedicalTest,
|
||||
AddOrUpdateOptimumData,
|
||||
MarkerOptimums,
|
||||
AddOrUpdateAnalysisData,
|
||||
TreatmentCourseData,
|
||||
} from '../lib'
|
||||
|
||||
/**------------------ Surveys -------------------------- */
|
||||
export const fetchSurveys = (
|
||||
search?: MedicalAPI.GET.FetchSurveys.Params,
|
||||
): MedicalAPI.GET.FetchSurveys.Response =>
|
||||
medicalApi.get('survey', {
|
||||
params: { search },
|
||||
})
|
||||
|
||||
export const setSurveysToPatient = (
|
||||
data: MedicalAPI.PUT.SetSurveysToPatient.Params,
|
||||
): MedicalAPI.PUT.SetSurveysToPatient.Response =>
|
||||
medicalApi.put(`customer/${data.customer_id}/survey`, {
|
||||
survey_ids: data.survey_ids,
|
||||
})
|
||||
|
||||
export const fetchSurveyQuestions = (
|
||||
params: MedicalAPI.GET.FetchQuestionsOfSurvey.Params,
|
||||
): MedicalAPI.GET.FetchQuestionsOfSurvey.Response =>
|
||||
medicalApi.get(`survey/${params}`)
|
||||
|
||||
export const viewSurveyAnswers = (
|
||||
params: MedicalAPI.GET.FetchAnswersOfSurvey.Params,
|
||||
): MedicalAPI.GET.FetchAnswersOfSurvey.Response =>
|
||||
medicalApi.get(
|
||||
`customer/${params.customer_id}/survey/${params.survey_attemp_id}`,
|
||||
)
|
||||
|
||||
/**------------------ Reminder -------------------------- */
|
||||
export const addReminderToPatient = (
|
||||
data: MedicalAPI.POST.addReminderToPatient.Params,
|
||||
): MedicalAPI.POST.addReminderToPatient.Response =>
|
||||
medicalApi.post('eventReminder', data)
|
||||
|
||||
/**------------------ Treatmen Course -------------------------- */
|
||||
export const fetchTreatmentCourse = (
|
||||
search?: MedicalAPI.GET.FetchTreatmentCourse.Params,
|
||||
): MedicalAPI.GET.FetchTreatmentCourse.Response =>
|
||||
medicalApi.get('treatmentCourse', {
|
||||
params: { search },
|
||||
})
|
||||
|
||||
export const addTreatmentCourseToPatient = (
|
||||
data: MedicalAPI.POST.AddTreatmentCourseToPatient.Params,
|
||||
): MedicalAPI.POST.AddTreatmentCourseToPatient.Response =>
|
||||
medicalApi.post(`users/${data.user_id}/treatmentCourseUser`, data)
|
||||
|
||||
export const addFileToTreatment = (data: {
|
||||
user_id: number
|
||||
payload: FormData
|
||||
}): MedicalAPI.POST.AddTreatmentCourseToPatient.Response =>
|
||||
medicalApi.post(`users/${data.user_id}/treatmentCourseUser`, data.payload)
|
||||
|
||||
export const fetchPatientTreatmentCourse = (
|
||||
data: MedicalAPI.GET.FetchPatientTreatmentCourse.Params,
|
||||
): MedicalAPI.GET.FetchPatientTreatmentCourse.Response =>
|
||||
medicalApi.get(`users/${data}/treatmentCourseUser`)
|
||||
|
||||
export const deleteTreatmentCourseFromPatient = (
|
||||
data: MedicalAPI.DELETE.DeleteTreatmentCourseFromPatient.Params,
|
||||
): MedicalAPI.DELETE.DeleteTreatmentCourseFromPatient.Response =>
|
||||
medicalApi.delete(
|
||||
`users/${data.user_id}/treatmentCourseUser/${data.treatment_course_id}`,
|
||||
)
|
||||
|
||||
export const editTreatmentCourse = (
|
||||
data: MedicalAPI.PUT.UpdateTreatmentCourse.Params,
|
||||
): MedicalAPI.PUT.UpdateTreatmentCourse.Response =>
|
||||
medicalApi.put(
|
||||
`users/${data.user_id}/treatmentCourseUser/${data.treatment_course_id}`,
|
||||
data.payload,
|
||||
)
|
||||
|
||||
/**------------------ Appointment -------------------------- */
|
||||
|
||||
export const deleteAppointmentFromPatient = (
|
||||
data: MedicalAPI.DELETE.DeleteAppointmentFromPatient.Params,
|
||||
): MedicalAPI.DELETE.DeleteAppointmentFromPatient.Response =>
|
||||
medicalApi.delete(`appointment/${data.appointment}`)
|
||||
|
||||
/**------------------ Medical Test -------------------------- */
|
||||
|
||||
export const fetchMedicalTests =
|
||||
(): MedicalAPI.GET.FetchAllMedicalTest.Response =>
|
||||
medicalApi.get('listMedicalTest')
|
||||
|
||||
export const updateCustomOptimum = (
|
||||
data: MedicalAPI.POST.UpdateCustomOptimum.Params,
|
||||
): MedicalAPI.POST.UpdateCustomOptimum.Response =>
|
||||
medicalApi.post('optimalCustom', data)
|
||||
|
||||
export const addOrUpdateAnalysis = (
|
||||
data: MedicalAPI.POST.AddOrUpdateAnalysis.Params,
|
||||
): MedicalAPI.POST.AddOrUpdateAnalysis.Response =>
|
||||
medicalApi.post(`users/${data.user_id}/analysis`, data)
|
||||
|
||||
/**------------------ Health Matrix -------------------------- */
|
||||
export const updateHealthMatrixValue = (
|
||||
data: MedicalAPI.POST.UpdateHealthMatrixValue.Params,
|
||||
): MedicalAPI.POST.UpdateHealthMatrixValue.Response =>
|
||||
medicalApi.post(`appointment/${data.appointment_id}/healthMatrix`, data)
|
||||
|
||||
export namespace MedicalAPI {
|
||||
export namespace GET {
|
||||
export namespace FetchSurveys {
|
||||
export type Params = string
|
||||
export type Response = AxiosPromise<{
|
||||
data: Medical['survey_list']
|
||||
}>
|
||||
}
|
||||
|
||||
export namespace FetchTreatmentCourse {
|
||||
export type Params = string
|
||||
export type Response = AxiosPromise<{
|
||||
data: TreatmentCourse[]
|
||||
}>
|
||||
}
|
||||
|
||||
export namespace FetchPatientTreatmentCourse {
|
||||
export type Params = Patient['id']
|
||||
export type Response = AxiosPromise<{
|
||||
data: TreatmentCourse[]
|
||||
}>
|
||||
}
|
||||
|
||||
export namespace FetchAnswersOfSurvey {
|
||||
export type Params = ViewSurveyAnswersData
|
||||
export type Response = AxiosPromise<{
|
||||
data: SurveyDetail
|
||||
}>
|
||||
}
|
||||
|
||||
export namespace FetchQuestionsOfSurvey {
|
||||
export type Params = MedicalSurvey['id']
|
||||
export type Response = AxiosPromise<{
|
||||
data: SurveyDetail
|
||||
}>
|
||||
}
|
||||
|
||||
export namespace FetchAllMedicalTest {
|
||||
export type Response = AxiosPromise<{
|
||||
data: MedicalTest[]
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export namespace PUT {
|
||||
export namespace SetSurveysToPatient {
|
||||
export type Params = SetSurveyData
|
||||
export type Response = AxiosPromise<{
|
||||
data: any
|
||||
}>
|
||||
}
|
||||
|
||||
export namespace UpdateTreatmentCourse {
|
||||
export type Params = TreatmentCourseData
|
||||
export type Response = AxiosPromise<{
|
||||
data: PatientTreatmentCourse
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export namespace POST {
|
||||
export namespace addReminderToPatient {
|
||||
export type Params = AddReminderData
|
||||
export type Response = AxiosPromise<{
|
||||
data: PatientReminder
|
||||
}>
|
||||
}
|
||||
|
||||
export namespace AddTreatmentCourseToPatient {
|
||||
export type Params = AddTreatmentCourseData
|
||||
export type Response = AxiosPromise<{
|
||||
data: any
|
||||
}>
|
||||
}
|
||||
|
||||
export namespace UpdateCustomOptimum {
|
||||
export type Params = AddOrUpdateOptimumData
|
||||
export type Response = AxiosPromise<{
|
||||
data: MarkerOptimums
|
||||
}>
|
||||
}
|
||||
|
||||
export namespace AddOrUpdateAnalysis {
|
||||
export type Params = AddOrUpdateAnalysisData
|
||||
export type Response = AxiosPromise<{
|
||||
data: PatientAnalysis
|
||||
}>
|
||||
}
|
||||
export namespace UpdateHealthMatrixValue {
|
||||
export type Params = HealthMatrixData
|
||||
export type Response = AxiosPromise<{
|
||||
data: Maybe<THealthMatrix>
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export namespace DELETE {
|
||||
export namespace DeleteAppointmentFromPatient {
|
||||
export type Params = DeleteAppointmentData
|
||||
export type Response = AxiosPromise<{
|
||||
data: {
|
||||
success: boolean
|
||||
data: boolean
|
||||
message: null | string
|
||||
}
|
||||
}>
|
||||
}
|
||||
|
||||
export namespace DeleteTreatmentCourseFromPatient {
|
||||
export type Params = DeleteTreatmentCourseFromPatientData
|
||||
export type Response = AxiosPromise<{
|
||||
data: any
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
3
src/entities/medical/index.ts
Normal file
3
src/entities/medical/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './api'
|
||||
export * from './lib'
|
||||
export * from './model'
|
44
src/entities/medical/lib/helpers.ts
Normal file
44
src/entities/medical/lib/helpers.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import type { MedicalTest, BaseAnalysisOptimumValues } from './types'
|
||||
|
||||
export function setTestOptimums(
|
||||
list: MedicalTest[],
|
||||
patientInfo: {
|
||||
age: number
|
||||
sex: string
|
||||
},
|
||||
): BaseAnalysisOptimumValues[] {
|
||||
return list?.map(x => {
|
||||
const markesLength = x.markers?.length || 0
|
||||
let optimums
|
||||
let i
|
||||
markers: for (i = 0; i < markesLength; i++) {
|
||||
optimums = x.markers[i].optimums?.find(y => {
|
||||
const ages = y.age.split('-').map(Number)
|
||||
if (ages.length == 2) {
|
||||
return (
|
||||
ages[0] <= patientInfo.age &&
|
||||
patientInfo.age <= ages[1] &&
|
||||
y.sex == patientInfo.sex
|
||||
)
|
||||
} else if (ages.length == 1) {
|
||||
return (
|
||||
ages[0] <= patientInfo.age && y.sex == patientInfo.sex
|
||||
)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if (optimums && optimums.age) {
|
||||
break markers
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'base',
|
||||
test_id: Number(x.id),
|
||||
sex: patientInfo.sex,
|
||||
value: String(optimums?.age),
|
||||
}
|
||||
})
|
||||
}
|
2
src/entities/medical/lib/index.ts
Normal file
2
src/entities/medical/lib/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './types'
|
||||
export * from './helpers'
|
215
src/entities/medical/lib/types.ts
Normal file
215
src/entities/medical/lib/types.ts
Normal file
@ -0,0 +1,215 @@
|
||||
import type { Appointments, Patient, PatientAnalysis } from '@/entities'
|
||||
|
||||
export type Medical = {
|
||||
survey_list: MedicalSurvey[]
|
||||
treatment_courses: TreatmentCourse[]
|
||||
patient_treatment: TreatmentCourse[]
|
||||
survey: Maybe<SurveyDetail>
|
||||
medical_test: MedicalTest[]
|
||||
}
|
||||
|
||||
export type MedicalSurvey = {
|
||||
id: number
|
||||
title: string
|
||||
description: string
|
||||
questions_count: number | string
|
||||
}
|
||||
|
||||
export type SetSurveyData = {
|
||||
customer_id: number
|
||||
survey_ids: number[]
|
||||
}
|
||||
|
||||
export type AddReminderData = {
|
||||
performer_id: number // user id
|
||||
datetime: string
|
||||
type: 'appointment' | 'notice'
|
||||
text: string
|
||||
}
|
||||
|
||||
export type TreatmentCourse = {
|
||||
id: number
|
||||
title: string
|
||||
duration: number
|
||||
created_at: string | null
|
||||
updated_at: string | null
|
||||
nutrition: string | null
|
||||
medication: string | null
|
||||
buds: string | null
|
||||
analysis_and_research: string | null
|
||||
comment: string | null
|
||||
enable: 1 | 0
|
||||
}
|
||||
|
||||
export type SurveyDetail = {
|
||||
id: number
|
||||
title: string
|
||||
description: string
|
||||
questions: {
|
||||
id: number
|
||||
question_text: string
|
||||
question_type: 'text' | 'checkbox' | 'radio'
|
||||
survey_id: number
|
||||
options: {
|
||||
id: number
|
||||
option: string
|
||||
question_id: number
|
||||
sort: null
|
||||
is_selected?: boolean
|
||||
model?: any
|
||||
}[]
|
||||
answers?: {
|
||||
id: number
|
||||
question_id: number
|
||||
survey_attempt_id: number
|
||||
user_id: number
|
||||
answer_text: string
|
||||
question_option_id: number | null
|
||||
}[]
|
||||
}[]
|
||||
}
|
||||
|
||||
export type MedicalTest = {
|
||||
id: number
|
||||
title: string
|
||||
created_at: null | string
|
||||
updated_at: null | string
|
||||
unit: null | string
|
||||
markers: {
|
||||
id: number
|
||||
name: string
|
||||
list_medical_test_id: number
|
||||
unit: null | string
|
||||
created_at: null | string
|
||||
updated_at: null | string
|
||||
tip_min: null | string
|
||||
tip_max: null | string
|
||||
notice: null | string
|
||||
optimums: MarkerOptimums[]
|
||||
optimums_custom: MarkerOptimums[]
|
||||
head: string[]
|
||||
result: any[]
|
||||
}[]
|
||||
}
|
||||
|
||||
// For displaying analysis
|
||||
export type TestMarkers = {
|
||||
id: number
|
||||
name: string
|
||||
list_medical_test_id: number
|
||||
unit: null | string
|
||||
created_at: null | string
|
||||
updated_at: null | string
|
||||
tip_min: null | string
|
||||
tip_max: null | string
|
||||
notice: null | string
|
||||
optimums: MarkerOptimums | MarkerOptimums[]
|
||||
optimums_custom: any[]
|
||||
result?: PatientAnalysis[]
|
||||
}
|
||||
|
||||
export type MarkerOptimums = {
|
||||
id: number
|
||||
marker_id: number
|
||||
sex: string
|
||||
age: string
|
||||
min: number
|
||||
max: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
interval?: string
|
||||
}
|
||||
|
||||
export type BaseAnalysisOptimumValues = {
|
||||
type: 'base' | 'custom'
|
||||
test_id: MedicalTest['id']
|
||||
sex: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export type AddTreatmentCourseData = {
|
||||
user_id: number
|
||||
appointment_id: number
|
||||
treatment_course_id?: number
|
||||
enabled?: boolean | number
|
||||
file?: File
|
||||
}
|
||||
|
||||
export type DeleteAppointmentData = {
|
||||
appointment: Appointments['id']
|
||||
}
|
||||
|
||||
export type DeleteTreatmentCourseFromPatientData = {
|
||||
user_id: Patient['id']
|
||||
treatment_course_id: TreatmentCourse['id']
|
||||
}
|
||||
|
||||
export type ViewSurveyAnswersData = {
|
||||
customer_id: Patient['id']
|
||||
survey_attemp_id: MedicalSurvey['id']
|
||||
}
|
||||
|
||||
export type AddOrUpdateOptimumData = {
|
||||
list_medical_test_id: number
|
||||
marker_id: number
|
||||
sex: string
|
||||
age: string
|
||||
min: number
|
||||
max?: number
|
||||
}
|
||||
|
||||
export type AddOrUpdateAnalysisData = {
|
||||
list_medical_test_id: number
|
||||
result?: number | null
|
||||
quality: string
|
||||
date: string
|
||||
user_id: number
|
||||
marker_id: number
|
||||
}
|
||||
|
||||
export type HealthMatrixData = {
|
||||
appointment_id: number
|
||||
antecedents?: string | null
|
||||
triggers?: string | null
|
||||
medmators?: string | null
|
||||
nutrition?: string | null
|
||||
sleep?: string | null
|
||||
movement?: string | null
|
||||
stress?: string | null
|
||||
relation?: string | null
|
||||
assimilation?: string | null
|
||||
assimilation_color?: string | null
|
||||
energy?: string | null
|
||||
energy_color?: string | null
|
||||
inflammation?: string | null
|
||||
inflammation_color?: string | null
|
||||
structure?: string | null
|
||||
structure_color?: string | null
|
||||
mental?: string | null
|
||||
mental_color?: string | null
|
||||
communications?: string | null
|
||||
communications_color?: string | null
|
||||
transport?: string | null
|
||||
transport_color?: string | null
|
||||
detoxification?: string | null
|
||||
detoxification_color?: string | null
|
||||
circle_mental?: string | null
|
||||
circle_spiritual?: string | null
|
||||
circle_emotional?: string | null
|
||||
}
|
||||
|
||||
export type TreatmentCourseData = {
|
||||
user_id: Patient['id']
|
||||
treatment_course_id?: TreatmentCourse['id']
|
||||
payload?:
|
||||
| {
|
||||
nutrition?: TreatmentCourse['nutrition']
|
||||
medication?: TreatmentCourse['medication']
|
||||
buds?: TreatmentCourse['buds']
|
||||
analysis_and_research?: TreatmentCourse['analysis_and_research']
|
||||
comment?: TreatmentCourse['comment']
|
||||
title?: TreatmentCourse['title']
|
||||
enable?: TreatmentCourse['enable']
|
||||
}
|
||||
| FormData
|
||||
}
|
1
src/entities/medical/model/index.ts
Normal file
1
src/entities/medical/model/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './medical'
|
489
src/entities/medical/model/medical.ts
Normal file
489
src/entities/medical/model/medical.ts
Normal file
@ -0,0 +1,489 @@
|
||||
import { defineStore, storeToRefs } from 'pinia'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { toast } from 'vue3-toastify'
|
||||
import {
|
||||
addReminderToPatient,
|
||||
addTreatmentCourseToPatient,
|
||||
fetchMedicalTests,
|
||||
editTreatmentCourse,
|
||||
fetchPatientTreatmentCourse,
|
||||
fetchSurveyQuestions,
|
||||
fetchSurveys,
|
||||
fetchTreatmentCourse,
|
||||
setSurveysToPatient,
|
||||
setTestOptimums,
|
||||
updateCustomOptimum,
|
||||
viewSurveyAnswers,
|
||||
type PatientAnalysis,
|
||||
addFileToTreatment,
|
||||
} from '@/entities'
|
||||
import { usePatientStore } from '@/entities'
|
||||
import { Stores, declension } from '@/shared'
|
||||
import type {
|
||||
Medical,
|
||||
SetSurveyData,
|
||||
AddReminderData,
|
||||
AddTreatmentCourseData,
|
||||
ViewSurveyAnswersData,
|
||||
BaseAnalysisOptimumValues,
|
||||
MarkerOptimums,
|
||||
AddOrUpdateOptimumData,
|
||||
TreatmentCourseData,
|
||||
} from '../lib'
|
||||
|
||||
type MedicalState = BaseState<Maybe<Medical>>
|
||||
|
||||
export const useMedicalStore = defineStore(Stores.MEDICAL, () => {
|
||||
/**
|
||||
* State
|
||||
*/
|
||||
|
||||
const state: MedicalState = reactive({
|
||||
data: {
|
||||
survey_list: [],
|
||||
treatment_courses: [],
|
||||
patient_treatment: [],
|
||||
survey: null,
|
||||
medical_test: [],
|
||||
},
|
||||
loading: false,
|
||||
})
|
||||
|
||||
const currOptimums = ref<BaseAnalysisOptimumValues[]>([])
|
||||
const { analysisResults, infoForMedicalTest } = storeToRefs(
|
||||
usePatientStore(),
|
||||
)
|
||||
|
||||
/**
|
||||
* Getters
|
||||
*/
|
||||
|
||||
const surveyList = computed(() => {
|
||||
return state.data?.survey_list?.map(x => ({
|
||||
...x,
|
||||
questions_count: declension(Number(x.questions_count), [
|
||||
'вопрос',
|
||||
'вопроса',
|
||||
'вопросов',
|
||||
]),
|
||||
}))
|
||||
})
|
||||
|
||||
const treatmentCourses = computed(
|
||||
() =>
|
||||
state.data?.treatment_courses?.map(x => ({
|
||||
...x,
|
||||
duration: declension(x.duration, ['день', 'дня', 'дней']),
|
||||
})),
|
||||
)
|
||||
|
||||
const patientTreatments = computed(() => {
|
||||
return (
|
||||
state.data?.patient_treatment?.map(x => ({
|
||||
id: x.id,
|
||||
title: x.title,
|
||||
})) || []
|
||||
)
|
||||
})
|
||||
|
||||
const currSurvey = computed(() => state.data?.survey)
|
||||
|
||||
const medicalTestList = computed(() => {
|
||||
if (state.data?.medical_test && state.data.medical_test?.length) {
|
||||
const newList: any[] = []
|
||||
|
||||
state.data.medical_test.forEach((x, idx) => {
|
||||
const date: Set<string> = new Set()
|
||||
const markers: any[] = []
|
||||
|
||||
x.markers?.forEach(el => {
|
||||
const result: PatientAnalysis[] = []
|
||||
let optimums: MarkerOptimums | undefined
|
||||
let interval: string = ''
|
||||
|
||||
if (currOptimums.value[idx]['type'] == 'custom') {
|
||||
optimums = el.optimums_custom?.find(x => {
|
||||
return (
|
||||
String(
|
||||
currOptimums.value[idx]['value'],
|
||||
).includes(x.age) &&
|
||||
currOptimums.value[idx].sex == x.sex
|
||||
)
|
||||
})
|
||||
} else {
|
||||
optimums = el.optimums?.find(x => {
|
||||
return (
|
||||
String(
|
||||
currOptimums.value[idx]['value'],
|
||||
).includes(x.age) &&
|
||||
currOptimums.value[idx].sex == x.sex
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
analysisResults.value?.forEach(a => {
|
||||
if (a.marker_id === el.id) {
|
||||
let analysisState: string = ''
|
||||
|
||||
if (a.result) {
|
||||
if (
|
||||
optimums?.min &&
|
||||
optimums?.max &&
|
||||
!Number.isNaN(optimums?.min) &&
|
||||
!Number.isNaN(optimums?.max)
|
||||
) {
|
||||
if (optimums.min > a.result) {
|
||||
analysisState = 'down'
|
||||
} else if (
|
||||
optimums.min <= a.result &&
|
||||
a.result <= optimums.max
|
||||
) {
|
||||
analysisState = 'normal'
|
||||
} else {
|
||||
analysisState = 'up'
|
||||
}
|
||||
|
||||
interval = `${
|
||||
optimums?.min + '-' + optimums?.max
|
||||
}`
|
||||
} else if (optimums?.min) {
|
||||
if (optimums.min > a.result) {
|
||||
analysisState = 'down'
|
||||
} else if (optimums.min == a.result) {
|
||||
analysisState = 'normal'
|
||||
} else {
|
||||
analysisState = 'up'
|
||||
}
|
||||
interval = `${optimums?.min}`
|
||||
}
|
||||
}
|
||||
|
||||
date.add(a.date)
|
||||
|
||||
result.push({
|
||||
...a,
|
||||
state: analysisState,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
markers.push({
|
||||
id: el.id,
|
||||
name: el.name,
|
||||
unit: el.unit,
|
||||
list_medical_test_id: el.list_medical_test_id,
|
||||
tip_min: el.tip_min,
|
||||
tip_max: el.tip_max,
|
||||
notice: el.notice,
|
||||
result,
|
||||
optimums: {
|
||||
...optimums,
|
||||
interval,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
newList.push({
|
||||
id: x.id,
|
||||
title: x.title,
|
||||
unit: x.unit,
|
||||
analyze_date: [...date],
|
||||
markers,
|
||||
})
|
||||
})
|
||||
|
||||
return newList
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
const hasCustomGetters = (testId: number, markerId: number, age: string) =>
|
||||
computed(() => {
|
||||
const medicalTest = state.data?.medical_test.find(
|
||||
x => x.id == testId,
|
||||
)
|
||||
const testMarker = medicalTest?.markers.find(x => x.id == markerId)
|
||||
|
||||
return testMarker?.optimums_custom.find(x => x.age == age)
|
||||
})
|
||||
|
||||
const updateCurrOptimumsVal = (optimum: {
|
||||
value: string
|
||||
sex: string
|
||||
type: 'base' | 'custom'
|
||||
test_id: number
|
||||
}) => {
|
||||
currOptimums.value.forEach((el, idx) => {
|
||||
if (el.test_id == optimum.test_id) {
|
||||
currOptimums.value[idx] = {
|
||||
...optimum,
|
||||
sex:
|
||||
optimum.sex == 'children'
|
||||
? infoForMedicalTest.value.sex
|
||||
: optimum.sex,
|
||||
}
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Actions
|
||||
*/
|
||||
|
||||
const setSurveyList = async (search: string) => {
|
||||
try {
|
||||
state.loading = true
|
||||
const { data } = await fetchSurveys(search)
|
||||
|
||||
if (data.data && state.data?.survey_list) {
|
||||
state.data.survey_list = data.data
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
state.loading = false
|
||||
}
|
||||
|
||||
const setTreatmentCourseList = async (search: string) => {
|
||||
try {
|
||||
state.loading = true
|
||||
const { data } = await fetchTreatmentCourse(search)
|
||||
if (data.data?.length && state.data?.treatment_courses) {
|
||||
state.data.treatment_courses = data.data
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.log('e ->', e)
|
||||
}
|
||||
state.loading = false
|
||||
}
|
||||
|
||||
const setPatientTreatmentCourseList = async (payload: number) => {
|
||||
try {
|
||||
state.loading = true
|
||||
const { data } = await fetchPatientTreatmentCourse(payload)
|
||||
|
||||
if (data.data?.length && state.data?.patient_treatment) {
|
||||
state.data.patient_treatment = data.data
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.log('e ->', e)
|
||||
}
|
||||
state.loading = false
|
||||
}
|
||||
|
||||
const addDestinationToPatient = async (payload: AddTreatmentCourseData) => {
|
||||
try {
|
||||
state.loading = true
|
||||
const { data } = await addTreatmentCourseToPatient(payload)
|
||||
if (data.data) {
|
||||
usePatientStore().setDataToState(data.data)
|
||||
}
|
||||
|
||||
toast.success('Успешно сохранено!')
|
||||
} catch (e: any) {
|
||||
toast.error(`Что-то не так! ${e?.message || ''}`)
|
||||
}
|
||||
state.loading = false
|
||||
}
|
||||
|
||||
const addFileToTreatmentCourse = async (payload: any) => {
|
||||
try {
|
||||
state.loading = true
|
||||
const { data } = await addFileToTreatment(payload)
|
||||
if (data.data) {
|
||||
usePatientStore().setDataToState(data.data)
|
||||
}
|
||||
|
||||
toast.success('Успешно сохранено!')
|
||||
} catch (e: any) {
|
||||
toast.error(`Что-то не так! ${e?.message || ''}`)
|
||||
}
|
||||
state.loading = false
|
||||
}
|
||||
|
||||
const addSurveyToPatient = async (payload: SetSurveyData) => {
|
||||
try {
|
||||
state.loading = true
|
||||
const { data } = await setSurveysToPatient(payload)
|
||||
usePatientStore().setDataToState(data.data)
|
||||
toast.success('Успешно сохранено!')
|
||||
} catch (e: any) {
|
||||
toast.error(`Что-то не так! ${e?.message || ''}`)
|
||||
}
|
||||
state.loading = false
|
||||
}
|
||||
|
||||
const addReminder = async (payload: AddReminderData) => {
|
||||
try {
|
||||
state.loading = true
|
||||
const { data } = await addReminderToPatient(payload)
|
||||
|
||||
usePatientStore().setReminderToState(data.data)
|
||||
toast.success('Успешно сохранено!')
|
||||
} catch (e: any) {
|
||||
toast.error(`Что-то не так! ${e?.message || ''}`)
|
||||
}
|
||||
state.loading = false
|
||||
}
|
||||
|
||||
const viewSurvey = async (survey_id: number) => {
|
||||
state.loading = true
|
||||
try {
|
||||
state.loading = true
|
||||
const { data } = await fetchSurveyQuestions(survey_id)
|
||||
if (data.data && state.data) {
|
||||
state.data.survey = data.data
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast.error(`Что-то не так! ${e?.message || ''}`)
|
||||
}
|
||||
state.loading = false
|
||||
}
|
||||
|
||||
const viewSurveyResult = async (payload: ViewSurveyAnswersData) => {
|
||||
try {
|
||||
state.loading = true
|
||||
const { data } = await viewSurveyAnswers(payload)
|
||||
if (data.data && state.data) {
|
||||
state.data.survey = data.data
|
||||
|
||||
state.data.survey['questions'] = data.data.questions.map(x => ({
|
||||
...x,
|
||||
options: x.options.map(y => ({
|
||||
...y,
|
||||
model: y.is_selected ? y.id : '',
|
||||
})),
|
||||
}))
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast.error(`Что-то не так! ${e?.message || ''}`)
|
||||
}
|
||||
state.loading = false
|
||||
}
|
||||
|
||||
const setMedicalTest = async () => {
|
||||
try {
|
||||
state.loading = true
|
||||
const { data } = await fetchMedicalTests()
|
||||
if (data.data && data.data?.length && state.data?.medical_test) {
|
||||
currOptimums.value = setTestOptimums(
|
||||
data.data,
|
||||
infoForMedicalTest.value,
|
||||
)
|
||||
state.data.medical_test = data.data
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.log('e ->', e)
|
||||
}
|
||||
|
||||
state.loading = true
|
||||
}
|
||||
|
||||
/**
|
||||
* только для кастомных оптимумов
|
||||
* @param {AddOrUpdateOptimumData} payload
|
||||
*/
|
||||
const addOrUpdateCustomOptimum = async (
|
||||
payload: AddOrUpdateOptimumData,
|
||||
) => {
|
||||
try {
|
||||
const { data } = await updateCustomOptimum(payload)
|
||||
toast.success('Успешно сохранено!')
|
||||
|
||||
if (data.data && data.data?.marker_id) {
|
||||
const testIdx = state.data?.medical_test.length || 0
|
||||
let markerIdx, optimumIdx, i, j
|
||||
testLoop: for (i = 0; i < testIdx; i++) {
|
||||
if (
|
||||
state.data?.medical_test[i]['id'] ==
|
||||
payload.list_medical_test_id
|
||||
) {
|
||||
markerIdx =
|
||||
state.data?.medical_test[i]?.markers?.length || 0
|
||||
for (j = 0; j < markerIdx; j++) {
|
||||
if (
|
||||
state.data.medical_test[i]['markers'][j][
|
||||
'id'
|
||||
] == payload.marker_id
|
||||
) {
|
||||
optimumIdx =
|
||||
state.data.medical_test[i]['markers'][j]?.[
|
||||
'optimums_custom'
|
||||
]?.length || 0
|
||||
let hasOptimum = false
|
||||
for (let k = 0; k < optimumIdx; k++) {
|
||||
if (
|
||||
state.data.medical_test[i]['markers'][
|
||||
j
|
||||
]?.['optimums_custom'][k]['sex'] ==
|
||||
payload.sex &&
|
||||
state.data.medical_test[i]['markers'][
|
||||
j
|
||||
]?.['optimums_custom'][k]['age'] ==
|
||||
payload.age
|
||||
) {
|
||||
state.data.medical_test[i]['markers'][
|
||||
j
|
||||
]['optimums_custom'][k] = {
|
||||
...data.data,
|
||||
}
|
||||
hasOptimum = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasOptimum) {
|
||||
state.data.medical_test[i]['markers'][j][
|
||||
'optimums_custom'
|
||||
].push({
|
||||
...data.data,
|
||||
})
|
||||
|
||||
break testLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.log('e ->', e)
|
||||
}
|
||||
}
|
||||
|
||||
const updateTreatmentCourse = async (payload: TreatmentCourseData) => {
|
||||
try {
|
||||
const { data } = await editTreatmentCourse(payload)
|
||||
|
||||
if (data.data) {
|
||||
usePatientStore().setTreatmentDataToState(data.data)
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast.error(`Что-то не так! ${e?.message || ''}`)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
state,
|
||||
surveyList,
|
||||
currSurvey,
|
||||
treatmentCourses,
|
||||
patientTreatments,
|
||||
medicalTestList,
|
||||
currOptimums,
|
||||
hasCustomGetters,
|
||||
updateCurrOptimumsVal,
|
||||
setSurveyList,
|
||||
addSurveyToPatient,
|
||||
addReminder,
|
||||
setTreatmentCourseList,
|
||||
addDestinationToPatient,
|
||||
setPatientTreatmentCourseList,
|
||||
viewSurvey,
|
||||
viewSurveyResult,
|
||||
setMedicalTest,
|
||||
addOrUpdateCustomOptimum,
|
||||
updateTreatmentCourse,
|
||||
addFileToTreatmentCourse,
|
||||
}
|
||||
})
|
0
src/entities/medical/ui/index.ts
Normal file
0
src/entities/medical/ui/index.ts
Normal file
180
src/entities/patient/api/index.ts
Normal file
180
src/entities/patient/api/index.ts
Normal file
@ -0,0 +1,180 @@
|
||||
import { type AxiosPromise } from 'axios'
|
||||
import { baseApi, medicalApi } from '@/shared'
|
||||
import type {
|
||||
Appointments,
|
||||
EditAppointmentData,
|
||||
EditPatientData,
|
||||
Patient,
|
||||
PatientAnalysis,
|
||||
PatientMediaFile,
|
||||
} from '../lib'
|
||||
|
||||
/**------------------ Patient -------------------------- */
|
||||
export const fetchPatients = async ({
|
||||
search,
|
||||
page,
|
||||
per_page,
|
||||
}: PatientAPI.GET.FetchPatients.Params): PatientAPI.GET.FetchPatients.Response => {
|
||||
const response = await baseApi.get(
|
||||
`customer?search=${search}&page=${page}&perPage=${per_page}`,
|
||||
)
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
export const fetchPatient = async (
|
||||
data: PatientAPI.GET.FetchPatient.Params,
|
||||
): PatientAPI.GET.FetchPatient.Response => {
|
||||
const response = await baseApi.get(`customer/${data}`)
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
export const deletePatient = (data: PatientAPI.DELETE.DeletePatient.Params) =>
|
||||
new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
console.log('delete: ', data)
|
||||
|
||||
resolve(data)
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
export const editPatient = (
|
||||
data: PatientAPI.PUT.EditPatient.Params,
|
||||
): PatientAPI.PUT.EditPatient.Response =>
|
||||
baseApi.put(`customer/${data.id}`, data)
|
||||
|
||||
/**------------------ Patient Appointment -------------------------- */
|
||||
export const editPatientAppointment = (
|
||||
data: PatientAPI.PUT.EditAppointment.Params,
|
||||
): PatientAPI.PUT.EditAppointment.Response =>
|
||||
medicalApi.put(`appointment/${data.id}`, data)
|
||||
|
||||
export const addAppointmentToPatient = (
|
||||
data: PatientAPI.POST.AddAppointment.Params,
|
||||
): PatientAPI.POST.AddAppointment.Response =>
|
||||
medicalApi.post('appointment', data)
|
||||
|
||||
/**------------------ Set Avatar of Customer -------------------------- */
|
||||
export const setPatientAvatar = (
|
||||
data: PatientAPI.POST.SetAvatar.Params,
|
||||
): PatientAPI.POST.SetAvatar.Response => baseApi.post('user/setAvatar', data)
|
||||
|
||||
/**------------------ Media Files -------------------------- */
|
||||
export const setFilesToPatient = (
|
||||
data: PatientAPI.POST.SetFiles.Params,
|
||||
): PatientAPI.POST.SetFiles.Response =>
|
||||
medicalApi.post(`customer/${data.customer_id}/file`, data.files)
|
||||
|
||||
export const deleteMediaFile = async (
|
||||
data: PatientAPI.DELETE.DeleteFile.Params,
|
||||
) => medicalApi.delete(`file/${data}`)
|
||||
|
||||
/**------------------ Medical Tests -------------------------- */
|
||||
|
||||
export const fetchCustomerMedicalTest = (
|
||||
customer_id: PatientAPI.GET.FetchCustomerMedicalTests.Params,
|
||||
): PatientAPI.GET.FetchCustomerMedicalTests.Response =>
|
||||
medicalApi.get(`users/${customer_id}/analysis`)
|
||||
|
||||
/**
|
||||
* Module PatientAPI
|
||||
* Interfaces:
|
||||
* EditPatientParams
|
||||
* EditAppointmentParams
|
||||
*/
|
||||
interface EditPatientParams extends EditPatientData {
|
||||
id: number
|
||||
}
|
||||
|
||||
interface EditAppointmentParams extends EditAppointmentData {
|
||||
id: Appointments['id']
|
||||
user_id: Appointments['user_id']
|
||||
}
|
||||
export namespace PatientAPI {
|
||||
export namespace GET {
|
||||
export namespace FetchPatients {
|
||||
export type Params = {
|
||||
page: number
|
||||
per_page: number
|
||||
search: string
|
||||
}
|
||||
export type Response = AxiosPromise<PaginationData<Patient[]>>
|
||||
}
|
||||
|
||||
export namespace FetchPatient {
|
||||
export type Params = Patient['id']
|
||||
export type Response = AxiosPromise<Patient>
|
||||
|
||||
// export type Response = AxiosResponse<{
|
||||
// data: Patient[]
|
||||
// }>
|
||||
}
|
||||
|
||||
export namespace FetchCustomerMedicalTests {
|
||||
export type Params = Patient['id']
|
||||
export type Response = AxiosPromise<{
|
||||
data: PatientAnalysis[]
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export namespace DELETE {
|
||||
export namespace DeletePatient {
|
||||
export type Params = Patient['id']
|
||||
}
|
||||
|
||||
export namespace DeleteFile {
|
||||
export type Params = PatientMediaFile['id']
|
||||
}
|
||||
}
|
||||
|
||||
export namespace PUT {
|
||||
export namespace EditPatient {
|
||||
export type Params = EditPatientParams
|
||||
export type Response = AxiosPromise<{
|
||||
data: Patient
|
||||
}>
|
||||
}
|
||||
|
||||
export namespace EditAppointment {
|
||||
export type Params = EditAppointmentParams
|
||||
export type Response = AxiosPromise<{
|
||||
data: Appointments
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export namespace POST {
|
||||
export namespace AddAppointment {
|
||||
export type Params = {
|
||||
user_id: Appointments['user_id']
|
||||
}
|
||||
export type Response = AxiosPromise<{
|
||||
data: {
|
||||
user_id: number
|
||||
updated_at: string
|
||||
created_at: string
|
||||
id: number
|
||||
}
|
||||
}>
|
||||
}
|
||||
|
||||
export namespace SetAvatar {
|
||||
export type Params = FormData
|
||||
export type Response = AxiosPromise<{
|
||||
data: Patient
|
||||
}>
|
||||
}
|
||||
|
||||
export namespace SetFiles {
|
||||
export type Params = {
|
||||
customer_id: Patient['id']
|
||||
files: FormData
|
||||
}
|
||||
export type Response = AxiosPromise<{
|
||||
data: Patient
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
4
src/entities/patient/index.ts
Normal file
4
src/entities/patient/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './api'
|
||||
export * from './ui'
|
||||
export * from './lib'
|
||||
export * from './model'
|
65
src/entities/patient/lib/hooks.ts
Normal file
65
src/entities/patient/lib/hooks.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { fetchPatients } from '../api'
|
||||
import type { Patient, PatientsState, PatientTableRow } from '../lib'
|
||||
|
||||
export const useSearchPatient = (cb: (search: string) => void) => {
|
||||
let timeout: Timeout
|
||||
|
||||
const search = ref('')
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearTimeout(timeout)
|
||||
})
|
||||
|
||||
watch(search, value => {
|
||||
clearTimeout(timeout)
|
||||
|
||||
timeout = setTimeout(async () => {
|
||||
await cb(value)
|
||||
}, 300)
|
||||
})
|
||||
|
||||
return search
|
||||
}
|
||||
|
||||
export const useFetchPatients = async (
|
||||
search: string = '',
|
||||
page: Pagination['current_page'] = 1,
|
||||
state: PatientsState,
|
||||
converter: (list: Patient[]) => PatientTableRow[],
|
||||
) => {
|
||||
const { data } = await fetchPatients({
|
||||
page,
|
||||
per_page: state.pagination.per_page,
|
||||
search,
|
||||
})
|
||||
|
||||
const newData = converter(data.data)
|
||||
|
||||
return {
|
||||
pagination: data,
|
||||
data: !state.data || search ? newData : [...state.data, ...newData],
|
||||
}
|
||||
}
|
||||
|
||||
export const useBasePatientsLoad = (
|
||||
cb: (search?: string, page?: Pagination['current_page']) => Promise<void>,
|
||||
currentPage: Pagination['current_page'],
|
||||
lastPage: Pagination['last_page'],
|
||||
isFirstLoad: boolean,
|
||||
) => {
|
||||
const loadData = async ($state: any) => {
|
||||
await cb('', currentPage + 1)
|
||||
if (currentPage < lastPage) {
|
||||
$state.loaded()
|
||||
} else {
|
||||
$state.complete()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (isFirstLoad) await cb()
|
||||
})
|
||||
|
||||
return loadData
|
||||
}
|
2
src/entities/patient/lib/index.ts
Normal file
2
src/entities/patient/lib/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './types'
|
||||
export * from './hooks'
|
205
src/entities/patient/lib/types.ts
Normal file
205
src/entities/patient/lib/types.ts
Normal file
@ -0,0 +1,205 @@
|
||||
import type { LinkProps, UserBaseProps } from '@/shared'
|
||||
import type { PatientRequestProps } from '../ui'
|
||||
|
||||
export type Patient = {
|
||||
id: number
|
||||
sex: string
|
||||
avatar: string
|
||||
name: string
|
||||
gender: Gender
|
||||
anamnesis: string | null
|
||||
asking: string | null
|
||||
birthdate: string
|
||||
children_count: number
|
||||
city: string
|
||||
contact: string
|
||||
email: string
|
||||
marital: string
|
||||
profession: string
|
||||
media: PatientMediaFile[]
|
||||
medical_test: PatientMedicalTest[]
|
||||
survey_attempts: PatientSurvey[]
|
||||
files: PatientFiles[]
|
||||
health_matrix: number[]
|
||||
appointments: Appointments[]
|
||||
event_reminder_customer: PatientReminder[]
|
||||
}
|
||||
|
||||
export type PatientTableRow = {
|
||||
id: Patient['id']
|
||||
patient: UserBaseProps
|
||||
age: number
|
||||
gender: Gender
|
||||
contact?: LinkProps
|
||||
time?: string
|
||||
actions?: boolean
|
||||
type?: string
|
||||
description?: string
|
||||
request?: PatientRequestProps['request']
|
||||
applicationDate?: string
|
||||
reminder?: string
|
||||
}
|
||||
|
||||
export type PatientsState = BaseStatePagination<Maybe<PatientTableRow[]>>
|
||||
|
||||
export enum PatientStep {
|
||||
MAIN,
|
||||
QUESTIONNAIRE,
|
||||
ANALYZES,
|
||||
FILES,
|
||||
HEALTH_MATRIX,
|
||||
PURPOSE,
|
||||
}
|
||||
|
||||
export type PatientFiles = {
|
||||
id: number
|
||||
name: string
|
||||
file_name: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export type PatientSurvey = {
|
||||
id: number
|
||||
title: string
|
||||
survey_id: number
|
||||
attemp: number
|
||||
percent: number
|
||||
answers_count: number
|
||||
survey: {
|
||||
id: number
|
||||
title: string
|
||||
questions_count: number
|
||||
description: string
|
||||
}
|
||||
}
|
||||
|
||||
export type PatientMediaFile = {
|
||||
id: number
|
||||
file_name: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export type PatientMedicalTest = {
|
||||
id: number
|
||||
title: string
|
||||
created_at: string
|
||||
unit: string | null
|
||||
medical_tests: any[]
|
||||
}
|
||||
|
||||
export type Appointments = {
|
||||
id: number
|
||||
user_id: number
|
||||
complaint: string
|
||||
taking_medication: string
|
||||
taking_bud: string
|
||||
physical_activity: string
|
||||
stress: string
|
||||
sleep: string
|
||||
bad_habits: string
|
||||
conclusion: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
treatment_course_user?: PatientTreatmentCourse
|
||||
health_matrix: THealthMatrix
|
||||
}
|
||||
|
||||
export type THealthMatrix = {
|
||||
id: number
|
||||
appointment_id: number
|
||||
antecedents: string | null
|
||||
triggers: string | null
|
||||
medmators: string | null
|
||||
nutrition: string | null
|
||||
sleep: string | null
|
||||
movement: string | null
|
||||
stress: string | null
|
||||
relation: string | null
|
||||
assimilation: string | null
|
||||
assimilation_color: string | null
|
||||
energy: string | null
|
||||
energy_color: string | null
|
||||
inflammation: string | null
|
||||
inflammation_color: string | null
|
||||
structure: string | null
|
||||
structure_color: string | null
|
||||
mental: string | null
|
||||
mental_color: string | null
|
||||
communications: string | null
|
||||
communications_color: string | null
|
||||
transport: string | null
|
||||
transport_color: string | null
|
||||
detoxification: string | null
|
||||
detoxification_color: string | null
|
||||
circle_mental: string | null
|
||||
circle_spiritual: string | null
|
||||
circle_emotional: string | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export type EditPatientData = {
|
||||
name?: string
|
||||
sex?: string
|
||||
city?: string
|
||||
asking?: string
|
||||
marital?: string
|
||||
contact?: string
|
||||
anamnesis?: string
|
||||
birthdate?: string
|
||||
profession?: string
|
||||
children_count?: string
|
||||
}
|
||||
|
||||
export type EditAppointmentData = {
|
||||
complaint?: Appointments['complaint']
|
||||
taking_medication?: Appointments['taking_medication']
|
||||
taking_bud?: Appointments['taking_bud']
|
||||
physical_activity?: Appointments['physical_activity']
|
||||
stress?: Appointments['stress']
|
||||
sleep?: Appointments['sleep']
|
||||
bad_habits?: Appointments['bad_habits']
|
||||
conclusion?: Appointments['conclusion']
|
||||
}
|
||||
|
||||
export type PatientReminder = {
|
||||
id: number
|
||||
text: string
|
||||
type: string
|
||||
user_id: number
|
||||
performer_id: number
|
||||
datetime: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export type PatientTreatmentCourse = {
|
||||
id: number
|
||||
title: string
|
||||
duration: number | null
|
||||
user_id: number
|
||||
performer_id: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
appointment_id: number
|
||||
nutrition: string | null
|
||||
medication: string
|
||||
buds: string | null
|
||||
analysis_and_research: string | null
|
||||
comment: string | null
|
||||
media: PatientFiles[]
|
||||
enabled: number
|
||||
}
|
||||
|
||||
export type PatientAnalysis = {
|
||||
id?: number
|
||||
user_id: number
|
||||
performer_id?: number
|
||||
result: number | null
|
||||
marker_id: number
|
||||
quality: string
|
||||
date: string
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
state?: string
|
||||
}
|
2
src/entities/patient/model/converters/index.ts
Normal file
2
src/entities/patient/model/converters/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './patientsToMyPatients'
|
||||
export * from './patientsToRequestsPatients'
|
@ -0,0 +1,20 @@
|
||||
import { dateToAge } from '@/shared'
|
||||
import type { Patient, PatientTableRow } from '../../lib'
|
||||
|
||||
export const patientsToMyPatients = (list: Patient[]): PatientTableRow[] => {
|
||||
return list.map(item => ({
|
||||
id: item.id,
|
||||
patient: {
|
||||
name: item.name || '',
|
||||
avatar: item.avatar || '',
|
||||
},
|
||||
gender: 1,
|
||||
age: dateToAge(item.birthdate),
|
||||
request: item.asking || '',
|
||||
reminder: 'моковые данные, с бека не приходит!!!',
|
||||
contact: {
|
||||
href: `tel:${item.contact || ''}`,
|
||||
text: item.contact || '',
|
||||
},
|
||||
}))
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import { dateToAge } from '@/shared'
|
||||
import type { Patient, PatientTableRow } from '../../lib'
|
||||
|
||||
export const patientsToRequestsPatients = (
|
||||
list: Patient[],
|
||||
): PatientTableRow[] => {
|
||||
return list.map(item => ({
|
||||
id: item.id,
|
||||
patient: {
|
||||
name: item.name || '',
|
||||
avatar: item.avatar || '',
|
||||
},
|
||||
gender: 1,
|
||||
age: dateToAge(item.birthdate),
|
||||
applicationDate: '2023-10-23T05:57:37.000000Z',
|
||||
request: item.asking || '',
|
||||
actions: true,
|
||||
}))
|
||||
}
|
1
src/entities/patient/model/index.ts
Normal file
1
src/entities/patient/model/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './module'
|
3
src/entities/patient/model/module/index.ts
Normal file
3
src/entities/patient/model/module/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './my-patients'
|
||||
export * from './request-patients'
|
||||
export * from './patient'
|
54
src/entities/patient/model/module/my-patients.ts
Normal file
54
src/entities/patient/model/module/my-patients.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { reactive } from 'vue'
|
||||
import { patientsToMyPatients } from '@/entities/patient/model/converters'
|
||||
import { Stores } from '@/shared'
|
||||
import { deletePatient } from '../../api'
|
||||
import type { Patient, PatientsState } from '../../lib'
|
||||
import { useFetchPatients } from '../../lib'
|
||||
|
||||
export const usePatientsStore = defineStore(Stores.MY_PATIENTS, () => {
|
||||
const state = reactive<PatientsState>({
|
||||
loading: false,
|
||||
data: null,
|
||||
pagination: {
|
||||
current_page: 1,
|
||||
per_page: 10,
|
||||
last_page: 1,
|
||||
},
|
||||
})
|
||||
|
||||
const setMyPatients = async (
|
||||
search: string = '',
|
||||
page: Pagination['current_page'] = 1,
|
||||
) => {
|
||||
try {
|
||||
const { data, pagination } = await useFetchPatients(
|
||||
search,
|
||||
page,
|
||||
state,
|
||||
patientsToMyPatients,
|
||||
)
|
||||
|
||||
state.pagination = pagination
|
||||
state.data = data
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteMyPatient = async (id: Patient['id']) => {
|
||||
try {
|
||||
await deletePatient(id)
|
||||
|
||||
state.data = state.data?.filter(item => item.id !== id)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
state,
|
||||
setMyPatients: setMyPatients,
|
||||
deleteMyPatient: deleteMyPatient,
|
||||
}
|
||||
})
|
562
src/entities/patient/model/module/patient.ts
Normal file
562
src/entities/patient/model/module/patient.ts
Normal file
@ -0,0 +1,562 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { toast } from 'vue3-toastify'
|
||||
import {
|
||||
editPatient,
|
||||
fetchPatient,
|
||||
editPatientAppointment,
|
||||
addAppointmentToPatient,
|
||||
setPatientAvatar,
|
||||
setFilesToPatient,
|
||||
deleteMediaFile,
|
||||
deleteAppointmentFromPatient,
|
||||
deleteTreatmentCourseFromPatient,
|
||||
updateHealthMatrixValue,
|
||||
type DeleteTreatmentCourseFromPatientData,
|
||||
fetchCustomerMedicalTest,
|
||||
addOrUpdateAnalysis,
|
||||
type AddOrUpdateAnalysisData,
|
||||
type HealthMatrixData,
|
||||
} from '@/entities'
|
||||
import {
|
||||
Stores,
|
||||
dateToAge,
|
||||
declension,
|
||||
formattingDateForClient,
|
||||
getTimeFromDate,
|
||||
prettifyDate,
|
||||
} from '@/shared'
|
||||
import {
|
||||
PatientStep,
|
||||
type Appointments,
|
||||
type EditAppointmentData,
|
||||
type EditPatientData,
|
||||
type Patient,
|
||||
type PatientReminder,
|
||||
type PatientMediaFile,
|
||||
type PatientAnalysis,
|
||||
type PatientTreatmentCourse,
|
||||
} from '../../lib'
|
||||
|
||||
type PatientState = BaseState<Maybe<Patient>>
|
||||
export const APPOINTMENTSITEMS: {
|
||||
key:
|
||||
| 'taking_medication'
|
||||
| 'taking_bud'
|
||||
| 'physical_activity'
|
||||
| 'stress'
|
||||
| 'sleep'
|
||||
| 'bad_habits'
|
||||
| 'complaint'
|
||||
name: any
|
||||
}[] = [
|
||||
{
|
||||
name: 'Прием медикаментов',
|
||||
key: 'taking_medication',
|
||||
},
|
||||
{
|
||||
name: 'Прием бадов',
|
||||
key: 'taking_bud',
|
||||
},
|
||||
{
|
||||
name: 'Физическая активность',
|
||||
key: 'physical_activity',
|
||||
},
|
||||
{
|
||||
name: 'Стресс',
|
||||
key: 'stress',
|
||||
},
|
||||
{
|
||||
name: 'Сон',
|
||||
key: 'sleep',
|
||||
},
|
||||
{
|
||||
name: 'Вредные привычки',
|
||||
key: 'bad_habits',
|
||||
},
|
||||
{
|
||||
name: 'Жалобы',
|
||||
key: 'complaint',
|
||||
},
|
||||
]
|
||||
|
||||
export const usePatientStore = defineStore(Stores.PATIENT, () => {
|
||||
/**-------- State -------------- */
|
||||
|
||||
const state = reactive<PatientState>({
|
||||
loading: false,
|
||||
data: null,
|
||||
})
|
||||
|
||||
const analysisResults = ref<PatientAnalysis[]>([])
|
||||
const idxAppointment = ref(0)
|
||||
const patientStep = ref<PatientStep>(PatientStep.MAIN)
|
||||
|
||||
/**-------- Action -------------- */
|
||||
const setCurrentPatient = async (id: Patient['id']) => {
|
||||
try {
|
||||
state.loading = true
|
||||
|
||||
const { data } = await fetchPatient(id)
|
||||
state.data = data
|
||||
idxAppointment.value = data?.appointments?.[0]?.id || 0
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
} finally {
|
||||
state.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
const resetCurrentPatient = () => {
|
||||
state.data = null
|
||||
}
|
||||
|
||||
const onEditPatient = async (payload: EditPatientData) => {
|
||||
try {
|
||||
const { data } = await editPatient({
|
||||
...payload,
|
||||
id: Number(state.data?.id),
|
||||
})
|
||||
state.data = data.data
|
||||
toast.success('Изменения сохранены !')
|
||||
} catch (e: any) {
|
||||
toast.error(`Что-то не так! ${e?.message || ''}`)
|
||||
console.log('e -> ', e)
|
||||
}
|
||||
state.loading = false
|
||||
}
|
||||
|
||||
const onEditAppointment = async (payload: EditAppointmentData) => {
|
||||
try {
|
||||
const { data } = await editPatientAppointment({
|
||||
...payload,
|
||||
id: Number(currAppointment.value?.id),
|
||||
user_id: Number(currAppointment.value?.user_id),
|
||||
})
|
||||
const appointment = data.data
|
||||
let appointmentID = -1
|
||||
state.data?.appointments.forEach((el, i) => {
|
||||
if (el.id == appointment.id) {
|
||||
appointmentID = i
|
||||
}
|
||||
})
|
||||
|
||||
if (typeof appointmentID == 'number' && appointmentID > -1) {
|
||||
APPOINTMENTSITEMS.forEach(elem => {
|
||||
if (state.data?.appointments[appointmentID]) {
|
||||
state.data.appointments[appointmentID][elem.key] =
|
||||
appointment[elem.key]
|
||||
}
|
||||
})
|
||||
}
|
||||
toast.success('Изменения сохранены!')
|
||||
} catch (e: any) {
|
||||
toast.error(`Что-то не так! ${e?.message || ''}`)
|
||||
console.log('e -> ', e)
|
||||
}
|
||||
}
|
||||
|
||||
const onCreateAppointment = async (payload: {
|
||||
user_id: Appointments['user_id']
|
||||
}) => {
|
||||
try {
|
||||
await addAppointmentToPatient(payload)
|
||||
await setCurrentPatient(payload.user_id)
|
||||
toast.success('Успешно добавлено новый прием!')
|
||||
} catch (e: any) {
|
||||
toast.error(`Что-то не так! ${e?.message || ''}`)
|
||||
console.log('e -> ', e)
|
||||
}
|
||||
}
|
||||
|
||||
const onUpdateAvatar = async (payload: FormData) => {
|
||||
try {
|
||||
const { data } = await setPatientAvatar(payload)
|
||||
if (data.data) {
|
||||
state.data = data.data
|
||||
}
|
||||
toast.success('Изменения сохранены')
|
||||
} catch (e: any) {
|
||||
toast.error(`Что-то не так! ${e?.message || ''}`)
|
||||
console.log('e -> ', e)
|
||||
}
|
||||
}
|
||||
|
||||
const onUploadFiles = async (payload: FormData) => {
|
||||
try {
|
||||
const { data } = await setFilesToPatient({
|
||||
customer_id: Number(state.data?.id),
|
||||
files: payload,
|
||||
})
|
||||
|
||||
if (data.data) {
|
||||
state.data = data.data
|
||||
}
|
||||
toast.success('Файлы успешно сохранены')
|
||||
} catch (e: any) {
|
||||
toast.error(`Что-то не так! ${e?.message || ''}`)
|
||||
console.log('e -> ', e)
|
||||
}
|
||||
}
|
||||
|
||||
const onDeleteMediaFile = async (payload: {
|
||||
id: PatientMediaFile['id']
|
||||
type: 'patient' | 'treatmentCourse'
|
||||
}) => {
|
||||
try {
|
||||
state.loading = true
|
||||
|
||||
await deleteMediaFile(payload.id)
|
||||
|
||||
if (payload.type == 'patient' && state.data?.media) {
|
||||
const filteredMedia = state.data.media.filter(
|
||||
file => file.id !== payload.id,
|
||||
)
|
||||
|
||||
state.data.media = filteredMedia
|
||||
}
|
||||
|
||||
if (payload.type == 'treatmentCourse') {
|
||||
state.data?.appointments?.forEach((app, idx) => {
|
||||
if (
|
||||
app.id == currAppointment.value?.id &&
|
||||
state.data?.appointments[idx]?.treatment_course_user
|
||||
) {
|
||||
const media =
|
||||
state.data.appointments[
|
||||
idx
|
||||
].treatment_course_user?.media.filter(
|
||||
x => x.id != payload.id,
|
||||
) || []
|
||||
|
||||
state.data.appointments[idx].treatment_course_user = {
|
||||
analysis_and_research:
|
||||
currAppointment.value.treatment_course_user
|
||||
?.analysis_and_research || '',
|
||||
appointment_id:
|
||||
currAppointment.value.treatment_course_user
|
||||
?.appointment_id || 0,
|
||||
buds:
|
||||
currAppointment.value.treatment_course_user
|
||||
?.buds || '[]',
|
||||
comment:
|
||||
currAppointment.value.treatment_course_user
|
||||
?.comment || '',
|
||||
created_at:
|
||||
currAppointment.value.treatment_course_user
|
||||
?.created_at || '',
|
||||
duration:
|
||||
currAppointment.value.treatment_course_user
|
||||
?.duration || 0,
|
||||
enabled:
|
||||
currAppointment.value.treatment_course_user
|
||||
?.enabled || 0,
|
||||
id:
|
||||
currAppointment.value.treatment_course_user
|
||||
?.id || 1,
|
||||
media: media,
|
||||
medication:
|
||||
currAppointment.value.treatment_course_user
|
||||
?.medication || '[]',
|
||||
nutrition:
|
||||
currAppointment.value.treatment_course_user
|
||||
?.nutrition || '',
|
||||
performer_id:
|
||||
currAppointment.value.treatment_course_user
|
||||
?.performer_id || 1,
|
||||
title:
|
||||
currAppointment.value.treatment_course_user
|
||||
?.title || '',
|
||||
updated_at:
|
||||
currAppointment.value.treatment_course_user
|
||||
?.updated_at || '',
|
||||
user_id:
|
||||
currAppointment.value.treatment_course_user
|
||||
?.user_id || 1,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
toast.success('Файл удален')
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
|
||||
toast.error('Произошла ошибка')
|
||||
} finally {
|
||||
state.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
const onDeleteAppointment = async (id: Appointments['id']) => {
|
||||
try {
|
||||
await deleteAppointmentFromPatient({ appointment: id })
|
||||
|
||||
if (state.data?.appointments) {
|
||||
state.data.appointments = state.data?.appointments.filter(
|
||||
x => x.id != id,
|
||||
)
|
||||
}
|
||||
if (idxAppointment.value == id) {
|
||||
idxAppointment.value = state.data?.appointments?.[0]?.id || 0
|
||||
}
|
||||
toast.success('Прием удален')
|
||||
} catch (e: any) {
|
||||
toast.error(`Что-то не так! ${e?.message || ''}`)
|
||||
console.log('e -> ', e)
|
||||
}
|
||||
}
|
||||
|
||||
const onDeleteTreatmentCourse = async (
|
||||
payload: DeleteTreatmentCourseFromPatientData,
|
||||
) => {
|
||||
try {
|
||||
await deleteTreatmentCourseFromPatient(payload)
|
||||
state.data?.appointments?.forEach((app, idx) => {
|
||||
if (
|
||||
app.id == currAppointment.value?.id &&
|
||||
state.data?.appointments[idx]?.treatment_course_user
|
||||
) {
|
||||
state.data.appointments[idx].treatment_course_user =
|
||||
undefined
|
||||
}
|
||||
})
|
||||
|
||||
toast.success('Назначения удален')
|
||||
} catch (e: any) {
|
||||
toast.error(`Что-то не так! ${e?.message || ''}`)
|
||||
console.log('e -> ', e)
|
||||
}
|
||||
}
|
||||
|
||||
const setPatientAnalysis = async () => {
|
||||
try {
|
||||
if (state.data?.id) {
|
||||
const { data } = await fetchCustomerMedicalTest(state.data.id)
|
||||
|
||||
if (data.data && Array.isArray(data.data)) {
|
||||
analysisResults.value = data.data
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.log('e -> ', e)
|
||||
}
|
||||
}
|
||||
|
||||
const updatePatientAnalysisResult = async (
|
||||
payload: AddOrUpdateAnalysisData,
|
||||
) => {
|
||||
try {
|
||||
const { data } = await addOrUpdateAnalysis(payload)
|
||||
const lenAnalysis = analysisResults.value.length
|
||||
let isChanged = false
|
||||
|
||||
for (let i = 0; i < lenAnalysis; i++) {
|
||||
if (
|
||||
analysisResults.value[i]['marker_id'] ==
|
||||
payload.marker_id &&
|
||||
analysisResults.value[i]['date'] == payload.date
|
||||
) {
|
||||
analysisResults.value[i]['result'] = Number(
|
||||
data?.data?.result || payload.result,
|
||||
)
|
||||
isChanged = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!isChanged) {
|
||||
// If analysisResult has not been changed, then this is a new analysis
|
||||
analysisResults.value.push(data.data)
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.log('e -> ', e)
|
||||
}
|
||||
}
|
||||
|
||||
const updateAnalysisDate = async (payload: AddOrUpdateAnalysisData) => {
|
||||
try {
|
||||
const { data } = await addOrUpdateAnalysis(payload)
|
||||
const lenAnalysis = analysisResults.value.length
|
||||
|
||||
for (let i = 0; i < lenAnalysis; i++) {
|
||||
if (
|
||||
analysisResults.value[i]['marker_id'] ==
|
||||
payload.marker_id &&
|
||||
analysisResults.value[i]['id'] == data.data.id
|
||||
) {
|
||||
analysisResults.value[i] = {
|
||||
...data.data,
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast.error(`Что-то не так! ${e?.message || ''}`)
|
||||
console.log('e -> ', e)
|
||||
}
|
||||
}
|
||||
|
||||
const onUpdateHealthMatrix = async (payload: HealthMatrixData) => {
|
||||
try {
|
||||
const { data } = await updateHealthMatrixValue(payload)
|
||||
if (data.data?.appointment_id) {
|
||||
for (let i = 0; i < appointmentLen.value; i++) {
|
||||
if (
|
||||
state.data?.appointments[i]?.health_matrix
|
||||
?.appointment_id == data.data.appointment_id
|
||||
) {
|
||||
state.data.appointments[i].health_matrix = {
|
||||
...data.data,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
toast.success('Изменения сохранены!')
|
||||
} catch (e: any) {
|
||||
toast.error(`Что-то не так! ${e?.message || ''}`)
|
||||
console.log('e -> ', e)
|
||||
}
|
||||
}
|
||||
|
||||
const setDataToState = (data: Patient) => {
|
||||
state.data = data
|
||||
}
|
||||
|
||||
const setReminderToState = (data: PatientReminder) => {
|
||||
state.data?.event_reminder_customer.push(data)
|
||||
}
|
||||
|
||||
const setTreatmentDataToState = (data: PatientTreatmentCourse) => {
|
||||
state.data?.appointments?.forEach((app, idx) => {
|
||||
if (
|
||||
app.id == currAppointment.value?.id &&
|
||||
state.data?.appointments[idx]
|
||||
) {
|
||||
state.data.appointments[idx].treatment_course_user = data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**-------- Getters -------------- */
|
||||
const appointmentLen = computed(() => state.data?.appointments?.length || 0)
|
||||
const patientInfo = computed(() => ({
|
||||
sex: state.data?.sex || '',
|
||||
name: state.data?.name || '',
|
||||
city: state.data?.city || '',
|
||||
avatar: state.data?.avatar || '',
|
||||
marital: state.data?.marital || '',
|
||||
contact: state.data?.contact || '',
|
||||
birthdate: state.data?.birthdate || '',
|
||||
profession: state.data?.profession || '',
|
||||
children_count: state.data?.children_count || '',
|
||||
birthday: prettifyDate(state.data?.birthdate) || '',
|
||||
age:
|
||||
declension(dateToAge(state.data?.birthdate || '0'), [
|
||||
'год',
|
||||
'года',
|
||||
'лет',
|
||||
]) || '',
|
||||
asking: state.data?.asking || '',
|
||||
anamnesis: state.data?.anamnesis || '',
|
||||
}))
|
||||
|
||||
const analyzes = computed(
|
||||
() =>
|
||||
state.data?.medical_test?.map(x => ({
|
||||
...x,
|
||||
created_at: prettifyDate(x.created_at),
|
||||
})) || [],
|
||||
)
|
||||
|
||||
const survey = computed(
|
||||
() =>
|
||||
state.data?.survey_attempts?.map(x => ({
|
||||
id: x.id,
|
||||
title: x.survey?.title,
|
||||
total: x.survey?.questions_count || 0,
|
||||
answers: x.answers_count || 0,
|
||||
percent: x.percent,
|
||||
})),
|
||||
)
|
||||
|
||||
const files = computed(() => state.data?.files)
|
||||
|
||||
const media = computed(() => state.data?.media)
|
||||
|
||||
const matrixHealth = computed(() => state.data?.health_matrix)
|
||||
|
||||
const reminders = computed(
|
||||
() =>
|
||||
state.data?.event_reminder_customer?.map(x => ({
|
||||
id: x.id,
|
||||
date: String(
|
||||
formattingDateForClient(x.datetime, 'short'),
|
||||
).replace(/\./g, ''),
|
||||
time: getTimeFromDate(x.datetime),
|
||||
type: x.type,
|
||||
name: x.text,
|
||||
})),
|
||||
)
|
||||
|
||||
const appointments = computed(
|
||||
() =>
|
||||
state.data?.appointments?.map(x => ({
|
||||
id: x.id,
|
||||
name: prettifyDate(x.created_at) || '',
|
||||
})),
|
||||
)
|
||||
|
||||
const currAppointment = computed(
|
||||
() => state.data?.appointments?.find(x => x.id == idxAppointment.value),
|
||||
)
|
||||
|
||||
const infoForMedicalTest = computed(
|
||||
(): {
|
||||
sex: string
|
||||
age: number
|
||||
} => ({
|
||||
sex: state.data?.sex || '',
|
||||
age: dateToAge(state.data?.birthdate || '0'),
|
||||
}),
|
||||
)
|
||||
|
||||
const treatmentCourse = computed((): PatientTreatmentCourse | null => {
|
||||
return currAppointment.value?.treatment_course_user || null
|
||||
})
|
||||
|
||||
return {
|
||||
state,
|
||||
media,
|
||||
files,
|
||||
survey,
|
||||
analyzes,
|
||||
reminders,
|
||||
patientInfo,
|
||||
patientStep,
|
||||
matrixHealth,
|
||||
appointments,
|
||||
idxAppointment,
|
||||
currAppointment,
|
||||
analysisResults,
|
||||
infoForMedicalTest,
|
||||
treatmentCourse,
|
||||
setCurrentPatient,
|
||||
resetCurrentPatient,
|
||||
onEditPatient,
|
||||
onEditAppointment,
|
||||
onCreateAppointment,
|
||||
setDataToState,
|
||||
setReminderToState,
|
||||
setTreatmentDataToState,
|
||||
onUpdateAvatar,
|
||||
onUploadFiles,
|
||||
onDeleteMediaFile,
|
||||
onDeleteAppointment,
|
||||
onDeleteTreatmentCourse,
|
||||
setPatientAnalysis,
|
||||
updatePatientAnalysisResult,
|
||||
updateAnalysisDate,
|
||||
onUpdateHealthMatrix,
|
||||
}
|
||||
})
|
44
src/entities/patient/model/module/request-patients.ts
Normal file
44
src/entities/patient/model/module/request-patients.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { reactive } from 'vue'
|
||||
import { Stores } from '@/shared'
|
||||
import { type PatientsState, useFetchPatients } from '../../lib'
|
||||
import { patientsToRequestsPatients } from '../converters'
|
||||
|
||||
export const useRequestPatientsStore = defineStore(
|
||||
Stores.REQUEST_PATIENTS,
|
||||
() => {
|
||||
const state = reactive<PatientsState>({
|
||||
loading: false,
|
||||
data: null,
|
||||
pagination: {
|
||||
current_page: 1,
|
||||
per_page: 10,
|
||||
last_page: 0,
|
||||
},
|
||||
})
|
||||
|
||||
const setRequestPatients = async (
|
||||
search: string = '',
|
||||
page: Pagination['current_page'] = 1,
|
||||
) => {
|
||||
try {
|
||||
const { data, pagination } = await useFetchPatients(
|
||||
search,
|
||||
page,
|
||||
state,
|
||||
patientsToRequestsPatients,
|
||||
)
|
||||
|
||||
state.pagination = pagination
|
||||
state.data = data
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
state,
|
||||
setRequestPatients,
|
||||
}
|
||||
},
|
||||
)
|
58
src/entities/patient/ui/EditableCard/EditableCard.scss
Normal file
58
src/entities/patient/ui/EditableCard/EditableCard.scss
Normal file
@ -0,0 +1,58 @@
|
||||
.editable__card {
|
||||
&--title {
|
||||
@include fontSize(
|
||||
b-16,
|
||||
(
|
||||
weight: 500,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
.card {
|
||||
gap: toRem(24);
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: $mainFontFamily;
|
||||
color: var(--dark-main);
|
||||
@include fontSize(
|
||||
s-13,
|
||||
(
|
||||
line-height: 1.3,
|
||||
)
|
||||
);
|
||||
white-space: pre-wrap;
|
||||
white-space: -moz-pre-wrap;
|
||||
white-space: -pre-wrap;
|
||||
white-space: -o-pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--dark-64);
|
||||
@include fontSize(
|
||||
s-13,
|
||||
(
|
||||
line-height: 1.3,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
textarea {
|
||||
border: none;
|
||||
overflow: auto;
|
||||
outline: none;
|
||||
@include fontSize(
|
||||
s-13,
|
||||
(
|
||||
line-height: 1.3,
|
||||
)
|
||||
);
|
||||
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
resize: none;
|
||||
color: var(--dark-main);
|
||||
}
|
||||
}
|
92
src/entities/patient/ui/EditableCard/EditableCard.vue
Normal file
92
src/entities/patient/ui/EditableCard/EditableCard.vue
Normal file
@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div
|
||||
:class="bem('card')"
|
||||
@focus="cardFocusEvent"
|
||||
@blur="cardBlurEvent"
|
||||
tabindex="0"
|
||||
>
|
||||
<card-component rounded hoverable size="m" :active="isFocused">
|
||||
<template #header>
|
||||
<h4 :class="bem('card--title')">{{ title }}</h4>
|
||||
</template>
|
||||
<template v-if="isFocused">
|
||||
<textarea
|
||||
ref="textarea"
|
||||
v-model="data"
|
||||
@blur="textareaBlurEvent"
|
||||
@focus="autoGrow"
|
||||
@input="autoGrow"
|
||||
>
|
||||
</textarea>
|
||||
</template>
|
||||
<template v-else>
|
||||
<pre v-if="value">{{ value }}</pre>
|
||||
<p v-else>Не заполнено</p>
|
||||
</template>
|
||||
</card-component>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" bem-block="Editable">
|
||||
import { ref, nextTick, watch } from 'vue'
|
||||
|
||||
export type EditableCardProps = {
|
||||
value: string
|
||||
title: string
|
||||
}
|
||||
|
||||
const props = defineProps<EditableCardProps>()
|
||||
const emit = defineEmits<{
|
||||
(e: 'save', val: string): void
|
||||
}>()
|
||||
|
||||
const isFocused = ref<boolean>(false)
|
||||
const data = ref<string>(props.value)
|
||||
const textarea = ref<HTMLElement>()
|
||||
|
||||
watch(props, () => {
|
||||
data.value = props.value
|
||||
})
|
||||
|
||||
/**------------- Methods --------------- */
|
||||
|
||||
const cardFocusEvent = (e: FocusEvent) => {
|
||||
const target = e.relatedTarget as HTMLElement
|
||||
if (isFocused.value && target?.tagName == 'TEXTAREA') {
|
||||
return
|
||||
}
|
||||
isFocused.value = true
|
||||
|
||||
nextTick(() => {
|
||||
const textarea = document.getElementsByTagName('textarea')?.[0]
|
||||
textarea?.focus()
|
||||
})
|
||||
}
|
||||
|
||||
const cardBlurEvent = (e: FocusEvent) => {
|
||||
const target = e.relatedTarget as HTMLElement
|
||||
if (target?.tagName !== 'TEXTAREA') {
|
||||
isFocused.value = false
|
||||
if (props.value != data.value) emit('save', data.value)
|
||||
}
|
||||
}
|
||||
|
||||
const textareaBlurEvent = (e: FocusEvent) => {
|
||||
const target = e.relatedTarget as HTMLElement
|
||||
if (target?.className !== 'patient-asking__card') {
|
||||
isFocused.value = false
|
||||
if (props.value != data.value) emit('save', data.value)
|
||||
}
|
||||
}
|
||||
|
||||
const autoGrow = () => {
|
||||
if (textarea.value) {
|
||||
const height = textarea.value.scrollHeight
|
||||
if (!data.value) {
|
||||
textarea.value.style.height = '17px'
|
||||
} else {
|
||||
textarea.value.style.height = 'auto'
|
||||
textarea.value.style.height = height + 'px'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
3
src/entities/patient/ui/EditableCard/index.ts
Normal file
3
src/entities/patient/ui/EditableCard/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import EditableCard, { type EditableCardProps } from './EditableCard.vue'
|
||||
|
||||
export { EditableCard, type EditableCardProps }
|
20
src/entities/patient/ui/EditableInput/EditableInput.scss
Normal file
20
src/entities/patient/ui/EditableInput/EditableInput.scss
Normal file
@ -0,0 +1,20 @@
|
||||
.editable-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
p {
|
||||
@include fontSize(s-13);
|
||||
cursor: pointer;
|
||||
padding: toRem(8) 0;
|
||||
&.empty {
|
||||
text-align: center;
|
||||
color: var(--dark-32);
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: toRem(6);
|
||||
border: 1px solid var(--brand-main);
|
||||
border-radius: $borderRadius6;
|
||||
}
|
||||
}
|
43
src/entities/patient/ui/EditableInput/EditableInput.vue
Normal file
43
src/entities/patient/ui/EditableInput/EditableInput.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div :class="bem()" tabindex="0" @focus="focusInput">
|
||||
<template v-if="isFocused">
|
||||
<input type="text" v-model="val" @blur="blurInput" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<p v-if="modelValue">{{ modelValue }}</p>
|
||||
<p v-else class="empty">{{ placeholder }}</p>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" bem-block="EditableInput">
|
||||
import { nextTick, ref } from 'vue'
|
||||
|
||||
export type EditableInputProps = {
|
||||
modelValue: any
|
||||
placeholder: string
|
||||
}
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const props = defineProps<EditableInputProps>()
|
||||
|
||||
const isFocused = ref<boolean>()
|
||||
const val = ref<string>('')
|
||||
|
||||
const focusInput = () => {
|
||||
isFocused.value = true
|
||||
val.value = props.modelValue
|
||||
|
||||
nextTick(() => {
|
||||
const input = document.querySelector(
|
||||
'.editable-input input',
|
||||
) as HTMLInputElement
|
||||
|
||||
input?.focus()
|
||||
})
|
||||
}
|
||||
|
||||
const blurInput = () => {
|
||||
emit('update:modelValue', val.value)
|
||||
isFocused.value = false
|
||||
}
|
||||
</script>
|
3
src/entities/patient/ui/EditableInput/index.ts
Normal file
3
src/entities/patient/ui/EditableInput/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import EditableInput, { type EditableInputProps } from './EditableInput.vue'
|
||||
|
||||
export { EditableInput, type EditableInputProps }
|
20
src/entities/patient/ui/EmptySurvey/EmptySurvey.scss
Normal file
20
src/entities/patient/ui/EmptySurvey/EmptySurvey.scss
Normal file
@ -0,0 +1,20 @@
|
||||
.empty-survey {
|
||||
aspect-ratio: 1 / 1.1;
|
||||
max-height: 252px;
|
||||
background: var(--brand-4-bg);
|
||||
border-radius: $borderRadius20;
|
||||
|
||||
&__title {
|
||||
@include fontSize(
|
||||
h3,
|
||||
(
|
||||
weight: 500,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
@include fontSize(b-14);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
11
src/entities/patient/ui/EmptySurvey/EmptySurvey.vue
Normal file
11
src/entities/patient/ui/EmptySurvey/EmptySurvey.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div :class="[...bem(), 'column', 'items-center', 'justify-center']">
|
||||
<span :class="bem('title')">Не назначен ни один опросник</span>
|
||||
<span :class="bem('subtitle')">
|
||||
Выберите опросник из библиотеки справа и назначьте пациенту
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" bem-block="EmptySurvey">
|
||||
export type EmptySurveyProps = {}
|
||||
</script>
|
3
src/entities/patient/ui/EmptySurvey/index.ts
Normal file
3
src/entities/patient/ui/EmptySurvey/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import EmptySurvey, { type EmptySurveyProps } from './EmptySurvey.vue'
|
||||
|
||||
export { EmptySurvey, type EmptySurveyProps }
|
@ -0,0 +1,28 @@
|
||||
.initial-appointment {
|
||||
aspect-ratio: 1 / 1.1;
|
||||
max-height: 390px;
|
||||
background: var(--brand-4-bg);
|
||||
border-radius: $borderRadius20;
|
||||
|
||||
&__title {
|
||||
@include fontSize(
|
||||
h2,
|
||||
(
|
||||
weight: 500,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
max-width: 330px;
|
||||
@include fontSize(b-14);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
margin-top: toRem(24);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div :class="[...bem(), 'column', 'items-center', 'justify-center']">
|
||||
<span :class="bem('title')">Первичный прием</span>
|
||||
<span :class="bem('subtitle')"
|
||||
>Добавьте карточку первичного приема, чтобы начать собирать данные о
|
||||
пациенте в динамике</span
|
||||
>
|
||||
<div :class="bem('actions')">
|
||||
<button-component
|
||||
text="Создать первичный прием"
|
||||
view="flat"
|
||||
rounded
|
||||
size="m"
|
||||
@click="onCreateAppointment({ user_id })"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" bem-block="InitialAppointment">
|
||||
import { useRoute } from 'vue-router'
|
||||
import { usePatientStore } from '@/entities'
|
||||
|
||||
export type InitialAppointmentProps = {}
|
||||
const user_id = Number(useRoute().params.id)
|
||||
|
||||
const { onCreateAppointment } = usePatientStore()
|
||||
</script>
|
5
src/entities/patient/ui/InitialAppointment/index.ts
Normal file
5
src/entities/patient/ui/InitialAppointment/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import InitialAppointment, {
|
||||
type InitialAppointmentProps,
|
||||
} from './InitialAppointment.vue'
|
||||
|
||||
export { InitialAppointment, type InitialAppointmentProps }
|
@ -0,0 +1,27 @@
|
||||
.initial-health-matrix {
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1.1;
|
||||
max-height: 413px;
|
||||
background: var(--brand-4-bg);
|
||||
border-radius: $borderRadius20;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
margin-top: toRem(45);
|
||||
|
||||
&__title {
|
||||
max-width: toRem(340);
|
||||
text-align: center;
|
||||
margin-bottom: toRem(24);
|
||||
|
||||
@include fontSize(
|
||||
h3,
|
||||
(
|
||||
weight: 700,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div :class="bem()">
|
||||
<span :class="bem('title')">
|
||||
Для добавления матрицы здоровья создайте первый прием
|
||||
</span>
|
||||
<button-component
|
||||
text="Создать прием"
|
||||
view="brand"
|
||||
rounded
|
||||
size="m"
|
||||
@click="onCreateAppointment({ user_id })"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" bem-block="InitialHealthMatrix">
|
||||
import { useRoute } from 'vue-router'
|
||||
import { usePatientStore } from '@/entities'
|
||||
|
||||
export type InitialHealthMatrixProps = {}
|
||||
const user_id = Number(useRoute().params.id)
|
||||
|
||||
const { onCreateAppointment } = usePatientStore()
|
||||
</script>
|
5
src/entities/patient/ui/InitialHealthMatrix/index.ts
Normal file
5
src/entities/patient/ui/InitialHealthMatrix/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import InitialHealthMatrix, {
|
||||
type InitialHealthMatrixProps,
|
||||
} from './InitialHealthMatrix.vue'
|
||||
|
||||
export { InitialHealthMatrix, type InitialHealthMatrixProps }
|
24
src/entities/patient/ui/InitialPurpose/InitialPurpose.scss
Normal file
24
src/entities/patient/ui/InitialPurpose/InitialPurpose.scss
Normal file
@ -0,0 +1,24 @@
|
||||
.initial-purpose {
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1.1;
|
||||
max-height: 413px;
|
||||
background: var(--brand-4-bg);
|
||||
border-radius: $borderRadius20;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: toRem(45);
|
||||
|
||||
&__title {
|
||||
max-width: toRem(340);
|
||||
text-align: center;
|
||||
margin-bottom: toRem(24);
|
||||
@include fontSize(
|
||||
h3,
|
||||
(
|
||||
weight: 700,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
27
src/entities/patient/ui/InitialPurpose/InitialPurpose.vue
Normal file
27
src/entities/patient/ui/InitialPurpose/InitialPurpose.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div :class="bem()">
|
||||
<span :class="bem('title')">
|
||||
Для добавления назначения создайте первый прием
|
||||
</span>
|
||||
|
||||
<button-component
|
||||
text="Создать прием"
|
||||
view="brand"
|
||||
rounded
|
||||
size="m"
|
||||
@click="onCreateAppointment({ user_id })"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" bem-block="InitialPurpose">
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import { usePatientStore } from '@/entities'
|
||||
|
||||
export type InitialPurposeProps = {}
|
||||
|
||||
const user_id = Number(useRoute().params.id)
|
||||
|
||||
const { onCreateAppointment } = usePatientStore()
|
||||
</script>
|
3
src/entities/patient/ui/InitialPurpose/index.ts
Normal file
3
src/entities/patient/ui/InitialPurpose/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import InitialPurpose, { type InitialPurposeProps } from './InitialPurpose.vue'
|
||||
|
||||
export { InitialPurpose, type InitialPurposeProps }
|
@ -0,0 +1,21 @@
|
||||
.patient-basic-info {
|
||||
.row-name {
|
||||
text-align: left;
|
||||
font-size: toRem(16);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
&__row[class$='unavailable'] {
|
||||
color: var(--dark-32);
|
||||
}
|
||||
|
||||
table {
|
||||
td,
|
||||
th {
|
||||
font-size: toRem(13);
|
||||
line-height: toRem(20);
|
||||
padding: toRem(4) 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
133
src/entities/patient/ui/PatientBasicInfo/PatientBasicInfo.vue
Normal file
133
src/entities/patient/ui/PatientBasicInfo/PatientBasicInfo.vue
Normal file
@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div :class="bem()">
|
||||
<card-component rounded size="m">
|
||||
<template #header>
|
||||
<div class="row justify-between items-center">
|
||||
<user-avatar
|
||||
:avatar="patientInfo.avatar"
|
||||
:user-id="userId"
|
||||
@update:avatar="updateAvatar"
|
||||
/>
|
||||
<button-component
|
||||
icon="pencil-line"
|
||||
view="secondary"
|
||||
size="m"
|
||||
@click="showEditPatientInfoModal"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<table>
|
||||
<tr v-for="(item, key) in data" :key="key">
|
||||
<th v-if="item.title" align="left">
|
||||
{{ item.title }}
|
||||
</th>
|
||||
<td
|
||||
align="right"
|
||||
:class="
|
||||
bem(`row row-${key}`, { unavailable: !item.value })
|
||||
"
|
||||
>
|
||||
<span v-if="item.value">{{ item.value }}</span>
|
||||
<span v-else>{{
|
||||
item?.placeholder || 'не указано'
|
||||
}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</card-component>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" bem-block="PatientBasicInfo">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ModalsName, useModalsStore } from '@/widgets'
|
||||
import { usePatientStore, type EditPatientData, UserAvatar } from '@/entities'
|
||||
|
||||
export type PatientBasicInfoProps = {}
|
||||
type DataType = {
|
||||
[key: string]: {
|
||||
title: string
|
||||
value: any
|
||||
placeholder?: string
|
||||
}
|
||||
}
|
||||
|
||||
const { patientInfo } = storeToRefs(usePatientStore())
|
||||
const data = computed<DataType>(() => ({
|
||||
name: {
|
||||
title: '',
|
||||
value: patientInfo.value.name,
|
||||
placeholder: 'ФИО',
|
||||
},
|
||||
sex: {
|
||||
title: 'Пол',
|
||||
value: patientInfo.value.sex == 'male' ? 'Мужчина' : 'Женщина',
|
||||
},
|
||||
age: {
|
||||
title: 'Возраст',
|
||||
value: patientInfo.value.age,
|
||||
},
|
||||
birthday: {
|
||||
title: 'Дата рождения',
|
||||
value: patientInfo.value.birthday,
|
||||
},
|
||||
city: {
|
||||
title: 'Город проживания',
|
||||
value: patientInfo.value.city,
|
||||
},
|
||||
marital: {
|
||||
title: 'Семейное положение',
|
||||
value: patientInfo.value.marital,
|
||||
},
|
||||
children_count: {
|
||||
title: 'Кол-во детей',
|
||||
value: patientInfo.value.children_count,
|
||||
},
|
||||
profession: {
|
||||
title: 'Профессия',
|
||||
value: patientInfo.value.profession,
|
||||
},
|
||||
contact: {
|
||||
title: 'Контакты',
|
||||
value: patientInfo.value.contact,
|
||||
},
|
||||
}))
|
||||
const userId = useRoute().params.id as string
|
||||
|
||||
/**------------ Methods ------------------ */
|
||||
const { onEditPatient, onUpdateAvatar } = usePatientStore()
|
||||
const modals = useModalsStore()
|
||||
|
||||
const updateAvatar = async (formdata: FormData) => {
|
||||
await onUpdateAvatar(formdata)
|
||||
}
|
||||
|
||||
const savePatientInfo = async (data: EditPatientData) => {
|
||||
modals.closeModal()
|
||||
await onEditPatient(data)
|
||||
}
|
||||
const showEditPatientInfoModal = () => {
|
||||
modals.setModal(ModalsName.EDITPATIENT, {
|
||||
title: 'Основная информация',
|
||||
data: {
|
||||
name: patientInfo.value.name,
|
||||
sex: patientInfo.value.sex,
|
||||
city: patientInfo.value.city,
|
||||
marital: patientInfo.value.marital,
|
||||
contact: patientInfo.value.contact,
|
||||
birthdate: patientInfo.value.birthdate,
|
||||
profession: patientInfo.value.profession,
|
||||
children_count: patientInfo.value.children_count,
|
||||
},
|
||||
success: {
|
||||
text: 'Сохранить',
|
||||
cb: savePatientInfo,
|
||||
},
|
||||
cansel: {
|
||||
text: 'Отмена',
|
||||
cb: modals.closeModal,
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
5
src/entities/patient/ui/PatientBasicInfo/index.ts
Normal file
5
src/entities/patient/ui/PatientBasicInfo/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import PatientBasicInfo, {
|
||||
type PatientBasicInfoProps,
|
||||
} from './PatientBasicInfo.vue'
|
||||
|
||||
export { PatientBasicInfo, type PatientBasicInfoProps }
|
@ -0,0 +1,64 @@
|
||||
.patient-files {
|
||||
&__card {
|
||||
&--title {
|
||||
@include fontSize(
|
||||
b-16,
|
||||
(
|
||||
weight: 500,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
&--button {
|
||||
height: 20px;
|
||||
font-size: toRem(13);
|
||||
padding: 0;
|
||||
&:hover {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
gap: toRem(20);
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: toRem(13);
|
||||
color: var(--dark-64);
|
||||
}
|
||||
}
|
||||
|
||||
&__list {
|
||||
padding: 0 !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: toRem(10);
|
||||
|
||||
&--item {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.file {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
@include fontSize(s-13);
|
||||
color: var(--dark-main) !important;
|
||||
|
||||
i {
|
||||
padding: 0 toRem(8) 0 0;
|
||||
}
|
||||
|
||||
&-name {
|
||||
width: 240px;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div :class="bem('card')">
|
||||
<card-component rounded size="m">
|
||||
<template #header>
|
||||
<div class="row justify-between items-center">
|
||||
<h4 :class="bem('card--title')">Файлы</h4>
|
||||
<slot name="add-file"></slot>
|
||||
</div>
|
||||
</template>
|
||||
<ul v-if="files?.length" :class="bem('list')">
|
||||
<li
|
||||
v-for="(f, i) in files"
|
||||
:key="`file-${i}`"
|
||||
:class="bem('list--item')"
|
||||
>
|
||||
<a
|
||||
:href="f.url"
|
||||
:class="bem(`list--item file`)"
|
||||
target="_blank"
|
||||
>
|
||||
<icon-base-component name="file" size="m" />
|
||||
<span :class="bem(`list--item file-name`)">{{
|
||||
f.file_name
|
||||
}}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p v-else>Не ни одного файлв</p>
|
||||
|
||||
<template v-if="files?.length" #actions>
|
||||
<button-component
|
||||
text="Посмотреть все"
|
||||
view="secondary"
|
||||
text-position="left"
|
||||
:class="bem('card--button')"
|
||||
@click="moveToFilesStep"
|
||||
/>
|
||||
</template>
|
||||
</card-component>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" bem-block="PatientFiles">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { usePatientStore, PatientStep } from '@/entities'
|
||||
|
||||
export type PatientFilesCardProps = {}
|
||||
|
||||
const { files, patientStep } = storeToRefs(usePatientStore())
|
||||
|
||||
const moveToFilesStep = () => {
|
||||
patientStep.value = PatientStep.FILES
|
||||
}
|
||||
</script>
|
5
src/entities/patient/ui/PatientFilesCard/index.ts
Normal file
5
src/entities/patient/ui/PatientFilesCard/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import PatientFilesCard, {
|
||||
type PatientFilesCardProps,
|
||||
} from './PatientFilesCard.vue'
|
||||
|
||||
export { PatientFilesCard, type PatientFilesCardProps }
|
@ -0,0 +1,51 @@
|
||||
.health-matrix {
|
||||
&__card {
|
||||
&--title {
|
||||
@include fontSize(
|
||||
b-16,
|
||||
(
|
||||
weight: 500,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
.card {
|
||||
gap: toRem(20);
|
||||
}
|
||||
}
|
||||
|
||||
&__graph {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
&--item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&:first-child .overlay {
|
||||
border-radius: toRem(24) 0 0 toRem(24);
|
||||
}
|
||||
|
||||
&:last-child .overlay {
|
||||
border-radius: 0 toRem(24) toRem(24) 0;
|
||||
}
|
||||
}
|
||||
|
||||
.overlay {
|
||||
height: toRem(24);
|
||||
}
|
||||
|
||||
.value {
|
||||
display: block;
|
||||
padding: toRem(6) 0 0;
|
||||
text-align: center;
|
||||
@include fontSize(
|
||||
s-13,
|
||||
(
|
||||
line-height: toRem(20),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div v-if="matrixHealth?.length" :class="bem('card')">
|
||||
<card-component rounded size="m">
|
||||
<template #header>
|
||||
<div class="row justify-between items-center">
|
||||
<h4 :class="bem('card--title')">Матрица здоровья</h4>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div :class="bem('graph')">
|
||||
<div
|
||||
v-for="(m, i) in matrixHealth"
|
||||
:key="i"
|
||||
:class="bem('graph', 'item')"
|
||||
:style="`flex: 0 0 ${m}%;`"
|
||||
>
|
||||
<div
|
||||
class="overlay"
|
||||
:style="`background: ${getMatrixColor(i)}`"
|
||||
></div>
|
||||
<span class="value">{{ m }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #actions>
|
||||
<button-component
|
||||
text="Подробнее "
|
||||
view="secondary"
|
||||
size="m"
|
||||
width="88px"
|
||||
:class="bem('card--button')"
|
||||
@click="moveToHealthMatrix"
|
||||
/>
|
||||
</template>
|
||||
</card-component>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" bem-block="healthMatrix">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { usePatientStore, PatientStep } from '@/entities'
|
||||
|
||||
export type PatientHealthMatrixProps = {}
|
||||
|
||||
const colors = ['#f25c80', '#fe9b7d', '#b2d677']
|
||||
const { matrixHealth, patientStep } = storeToRefs(usePatientStore())
|
||||
const getMatrixColor = (idx: number): string => {
|
||||
if (matrixHealth.value?.[idx]) return colors?.[idx]
|
||||
|
||||
return '#' + Math.floor(Math.random() * 16777215).toString(16)
|
||||
}
|
||||
const moveToHealthMatrix = () => {
|
||||
patientStep.value = PatientStep.HEALTH_MATRIX
|
||||
}
|
||||
</script>
|
5
src/entities/patient/ui/PatientHealthMatrix/index.ts
Normal file
5
src/entities/patient/ui/PatientHealthMatrix/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import PatientHealthMatrix, {
|
||||
type PatientHealthMatrixProps,
|
||||
} from './PatientHealthMatrix.vue'
|
||||
|
||||
export { PatientHealthMatrix, type PatientHealthMatrixProps }
|
@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<tabs-component v-model="valueModel" :list="list" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" bem-block="PatientNavigation">
|
||||
import { computed } from 'vue'
|
||||
import type { TabsProps } from '@/shared'
|
||||
import { PatientStep } from '../../lib'
|
||||
|
||||
export type PatientNavigationProps = {
|
||||
modelValue: PatientStep
|
||||
}
|
||||
|
||||
type Emits = {
|
||||
(e: 'update:modelValue', value: PatientStep): PatientStep
|
||||
}
|
||||
|
||||
const props = defineProps<PatientNavigationProps>()
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const valueModel = computed({
|
||||
get: () => props.modelValue,
|
||||
set: value => emit('update:modelValue', value),
|
||||
})
|
||||
|
||||
const list: TabsProps['list'] = [
|
||||
{
|
||||
id: PatientStep.MAIN,
|
||||
text: 'Карточка',
|
||||
},
|
||||
{
|
||||
id: PatientStep.QUESTIONNAIRE,
|
||||
text: 'Опросники',
|
||||
},
|
||||
{
|
||||
id: PatientStep.ANALYZES,
|
||||
text: 'Анализы',
|
||||
},
|
||||
{
|
||||
id: PatientStep.FILES,
|
||||
text: 'Файлы',
|
||||
},
|
||||
{
|
||||
id: PatientStep.HEALTH_MATRIX,
|
||||
text: 'Матрица здоровья',
|
||||
},
|
||||
{
|
||||
id: PatientStep.PURPOSE,
|
||||
text: 'Назначение',
|
||||
},
|
||||
]
|
||||
</script>
|
5
src/entities/patient/ui/PatientNavigation/index.ts
Normal file
5
src/entities/patient/ui/PatientNavigation/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import PatientNavigation, {
|
||||
type PatientNavigationProps,
|
||||
} from './PatientNavigation.vue'
|
||||
|
||||
export { PatientNavigation, type PatientNavigationProps }
|
@ -0,0 +1,69 @@
|
||||
.patient-reminders {
|
||||
&__card {
|
||||
&--title {
|
||||
@include fontSize(
|
||||
b-16,
|
||||
(
|
||||
weight: 500,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
&--button {
|
||||
height: 20px;
|
||||
font-size: toRem(13);
|
||||
padding: 0;
|
||||
&:hover {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
gap: toRem(20);
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: toRem(13);
|
||||
color: var(--dark-64);
|
||||
}
|
||||
}
|
||||
|
||||
&__list {
|
||||
td,
|
||||
th {
|
||||
padding: toRem(8) 0;
|
||||
@include fontSize(
|
||||
s-13,
|
||||
(
|
||||
line-height: 1.5,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
th {
|
||||
padding-right: toRem(4);
|
||||
}
|
||||
|
||||
span {
|
||||
min-width: toRem(50);
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--dark-64);
|
||||
}
|
||||
|
||||
mark {
|
||||
min-width: toRem(50);
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 toRem(4);
|
||||
color: var(--dark-main);
|
||||
background: var(--blue-20);
|
||||
border-radius: 3px;
|
||||
@include fontSize(s-13);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div :class="bem('card')">
|
||||
<card-component rounded size="m">
|
||||
<template #header>
|
||||
<div class="row justify-between items-center">
|
||||
<h4 :class="bem('card--title')">Напоминания</h4>
|
||||
<button-component
|
||||
icon="plus"
|
||||
view="secondary"
|
||||
size="xs"
|
||||
@click="showAddReminderModal"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<table v-if="reminders?.length" :class="bem('list')">
|
||||
<tr
|
||||
v-for="(r, i) in reminders"
|
||||
:key="`reminder_${i}`"
|
||||
:class="bem('list--item')"
|
||||
>
|
||||
<th align="left">{{ r.name || '--' }}</th>
|
||||
<td align="right">
|
||||
<span>{{ r.date }}</span>
|
||||
<mark>{{ r.time }}</mark>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p v-else>Не ни одного напоминания</p>
|
||||
|
||||
<template v-if="reminders?.length" #actions>
|
||||
<button-component
|
||||
text="Посмотреть все"
|
||||
view="secondary"
|
||||
text-position="left"
|
||||
:class="bem('card--button')"
|
||||
@click="moveToPurposeStep"
|
||||
/>
|
||||
</template>
|
||||
</card-component>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" bem-block="PatientReminders">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ModalsName, useModalsStore } from '@/widgets'
|
||||
import { usePatientStore, useMedicalStore, PatientStep } from '@/entities'
|
||||
|
||||
export type PatientRemindersProps = {}
|
||||
|
||||
const { reminders, patientStep } = storeToRefs(usePatientStore())
|
||||
const modals = useModalsStore()
|
||||
const { addReminder } = useMedicalStore()
|
||||
const performerId = useRoute().params.id
|
||||
|
||||
const onAddReminder = async (data: {
|
||||
datetime: string
|
||||
type: 'appointment' | 'notice'
|
||||
text: string
|
||||
}) => {
|
||||
await addReminder({
|
||||
...data,
|
||||
performer_id: Number(performerId),
|
||||
})
|
||||
modals.closeModal()
|
||||
}
|
||||
|
||||
const showAddReminderModal = () => {
|
||||
modals.setModal(ModalsName.ADDREMINDER, {
|
||||
title: 'Создать напоминание',
|
||||
success: {
|
||||
text: 'Сохранить',
|
||||
cb: onAddReminder,
|
||||
},
|
||||
cansel: {
|
||||
text: 'Отмена',
|
||||
cb: modals.closeModal,
|
||||
},
|
||||
})
|
||||
}
|
||||
const moveToPurposeStep = () => {
|
||||
patientStep.value = PatientStep.PURPOSE
|
||||
}
|
||||
</script>
|
5
src/entities/patient/ui/PatientReminders/index.ts
Normal file
5
src/entities/patient/ui/PatientReminders/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import PatientReminders, {
|
||||
type PatientRemindersProps,
|
||||
} from './PatientReminders.vue'
|
||||
|
||||
export { PatientReminders, type PatientRemindersProps }
|
10
src/entities/patient/ui/PatientRequest/PatientRequest.scss
Normal file
10
src/entities/patient/ui/PatientRequest/PatientRequest.scss
Normal file
@ -0,0 +1,10 @@
|
||||
.patient-request {
|
||||
display: flex;
|
||||
gap: toRem(6);
|
||||
|
||||
&__tooltip-content {
|
||||
@include column(toRem(12));
|
||||
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
37
src/entities/patient/ui/PatientRequest/PatientRequest.vue
Normal file
37
src/entities/patient/ui/PatientRequest/PatientRequest.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div :class="bem()">
|
||||
<tag-component v-if="request.length" :text="request[0]" />
|
||||
<tooltip-component
|
||||
v-if="request.length > 1"
|
||||
title="Запрос"
|
||||
icon="bell"
|
||||
:class="bem('tooltip')"
|
||||
>
|
||||
<template v-slot:parent>
|
||||
<tag-component :text="countText" hoverable view="grey" />
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<div :class="bem('tooltip-content')">
|
||||
<tag-component
|
||||
v-for="text in request"
|
||||
:key="text"
|
||||
:text="text"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tooltip-component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" bem-block="patient-request">
|
||||
import { computed } from 'vue'
|
||||
|
||||
export type PatientRequestProps = {
|
||||
// FIXME change type on Request[]
|
||||
request: string
|
||||
}
|
||||
|
||||
const props = defineProps<PatientRequestProps>()
|
||||
|
||||
const countText = computed(() => `+${props.request.length - 1}`)
|
||||
</script>
|
3
src/entities/patient/ui/PatientRequest/index.ts
Normal file
3
src/entities/patient/ui/PatientRequest/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import PatientRequest, { type PatientRequestProps } from './PatientRequest.vue'
|
||||
|
||||
export { PatientRequest, type PatientRequestProps }
|
@ -0,0 +1,39 @@
|
||||
.patient-survey {
|
||||
&__card {
|
||||
&--title {
|
||||
@include fontSize(
|
||||
b-16,
|
||||
(
|
||||
weight: 500,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
&--button {
|
||||
height: 20px;
|
||||
font-size: toRem(13);
|
||||
padding: 0;
|
||||
&:hover {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
gap: toRem(20);
|
||||
}
|
||||
|
||||
table {
|
||||
td,
|
||||
th {
|
||||
font-size: toRem(13);
|
||||
line-height: toRem(20);
|
||||
padding: toRem(4) 0;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: toRem(13);
|
||||
color: var(--dark-64);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div :class="bem('card')">
|
||||
<card-component rounded size="m">
|
||||
<template #header>
|
||||
<div class="row justify-between items-center">
|
||||
<h4 :class="bem('card--title')">Опросники</h4>
|
||||
<button-component
|
||||
icon="plus"
|
||||
view="secondary"
|
||||
size="xs"
|
||||
@click="openSelectQuestionnaireModal"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<table v-if="survey?.length">
|
||||
<tr v-for="(s, i) in survey" :key="i">
|
||||
<th align="left">{{ s.title }}</th>
|
||||
<td>
|
||||
<progress-bar
|
||||
height="5"
|
||||
:percent="s.percent"
|
||||
:total="s.total"
|
||||
:answers="s.answers"
|
||||
row
|
||||
></progress-bar>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p v-else>Пока нет ни одного опросника</p>
|
||||
|
||||
<template v-if="survey?.length" #actions>
|
||||
<button-component
|
||||
text="Посмотреть все"
|
||||
view="secondary"
|
||||
text-position="left"
|
||||
:class="bem('card--button')"
|
||||
@click="moveToQuestionnaireStep"
|
||||
/>
|
||||
</template>
|
||||
</card-component>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" bem-block="PatientSurvey">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useModalsStore, ModalsName } from '@/widgets'
|
||||
import {
|
||||
usePatientStore,
|
||||
ProgressBar,
|
||||
useMedicalStore,
|
||||
PatientStep,
|
||||
} from '@/entities'
|
||||
|
||||
export type PatientSurveyCardProps = {}
|
||||
|
||||
const { survey, patientStep } = storeToRefs(usePatientStore())
|
||||
const { surveyList } = storeToRefs(useMedicalStore())
|
||||
const customerId = useRoute().params.id
|
||||
|
||||
const { setModal, closeModal } = useModalsStore()
|
||||
const { setSurveyList, addSurveyToPatient } = useMedicalStore()
|
||||
|
||||
const sendSelectQuestionnaires = async (data: any[]) => {
|
||||
await addSurveyToPatient({
|
||||
customer_id: Number(customerId),
|
||||
survey_ids: data.map(x => Number(x)),
|
||||
})
|
||||
closeModal()
|
||||
}
|
||||
|
||||
const openSelectQuestionnaireModal = async () => {
|
||||
await setSurveyList('')
|
||||
setModal(ModalsName.SELECTQUESTIONAIRE, {
|
||||
title: 'Отправить опросник на заполнение',
|
||||
description: 'Все опросники',
|
||||
success: {
|
||||
text: 'Отправить',
|
||||
cb: sendSelectQuestionnaires,
|
||||
},
|
||||
cansel: {
|
||||
text: 'Отмена',
|
||||
cb: closeModal,
|
||||
},
|
||||
data: surveyList.value,
|
||||
})
|
||||
}
|
||||
|
||||
const moveToQuestionnaireStep = () => {
|
||||
patientStep.value = PatientStep.QUESTIONNAIRE
|
||||
}
|
||||
</script>
|
5
src/entities/patient/ui/PatientSurveyCard/index.ts
Normal file
5
src/entities/patient/ui/PatientSurveyCard/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import PatientSurveyCard, {
|
||||
type PatientSurveyCardProps,
|
||||
} from './PatientSurveyCard.vue'
|
||||
|
||||
export { PatientSurveyCard, type PatientSurveyCardProps }
|
27
src/entities/patient/ui/ProgressBar/ProgressBar.scss
Normal file
27
src/entities/patient/ui/ProgressBar/ProgressBar.scss
Normal file
@ -0,0 +1,27 @@
|
||||
.progress {
|
||||
display: flex;
|
||||
|
||||
&.row {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: toRem(16);
|
||||
}
|
||||
|
||||
&.column {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: toRem(8);
|
||||
}
|
||||
&__bar {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: var(--grey-main);
|
||||
}
|
||||
|
||||
.value {
|
||||
height: inherit;
|
||||
border-radius: inherit;
|
||||
background: var(--green-main);
|
||||
}
|
||||
}
|
50
src/entities/patient/ui/ProgressBar/ProgressBar.vue
Normal file
50
src/entities/patient/ui/ProgressBar/ProgressBar.vue
Normal file
@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div :class="classes">
|
||||
<div :class="bem('bar')" v-bind="barStyle">
|
||||
<div class="value" v-bind="barValueStyle"></div>
|
||||
</div>
|
||||
<span>{{ answers }}/{{ total }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import useBem from 'vue3-bem'
|
||||
|
||||
export type ProgressBarProps = {
|
||||
borderRadius?: string | number
|
||||
percent: string | number | null
|
||||
height?: string
|
||||
total: number
|
||||
answers: number
|
||||
row?: boolean
|
||||
column?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<ProgressBarProps>(), {
|
||||
height: '6',
|
||||
borderRadius: '25',
|
||||
row: false,
|
||||
column: false,
|
||||
})
|
||||
|
||||
/** --------------- State ---------------- */
|
||||
const bem = useBem('progress')
|
||||
const classes = computed(() => [
|
||||
...bem(),
|
||||
{ row: props.row },
|
||||
{ column: props.column },
|
||||
])
|
||||
|
||||
const barStyle = computed(() => ({
|
||||
style: {
|
||||
height: `${props.height}px`,
|
||||
'border-radius': `${props.borderRadius}px`,
|
||||
},
|
||||
}))
|
||||
|
||||
const barValueStyle = computed(() => ({
|
||||
style: {
|
||||
width: `${props.percent || 0}%`,
|
||||
},
|
||||
}))
|
||||
</script>
|
3
src/entities/patient/ui/ProgressBar/index.ts
Normal file
3
src/entities/patient/ui/ProgressBar/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import ProgressBar, { type ProgressBarProps } from './ProgressBar.vue'
|
||||
|
||||
export { ProgressBar, type ProgressBarProps }
|
@ -0,0 +1,27 @@
|
||||
.questionnaire-card {
|
||||
&__title {
|
||||
@include fontSize(
|
||||
b-16,
|
||||
(
|
||||
weight: 500,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
display: block;
|
||||
margin-top: toRem(8);
|
||||
@include fontSize(s-13);
|
||||
color: var(--dark-64);
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: toRem(20);
|
||||
}
|
||||
|
||||
.card {
|
||||
gap: toRem(20);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div :class="bem('')">
|
||||
<card-component rounded size="m">
|
||||
<template #header>
|
||||
<header>
|
||||
<h4 :class="bem('title')">{{ title }}</h4>
|
||||
<span v-if="!assigned" :class="bem('subtitle')">{{
|
||||
questions
|
||||
}}</span>
|
||||
</header>
|
||||
</template>
|
||||
<div v-if="assigned" :class="bem('body')">
|
||||
<progress-bar
|
||||
:answers="answers"
|
||||
:percent="percent"
|
||||
:total="Number(questions)"
|
||||
height="12"
|
||||
column
|
||||
/>
|
||||
</div>
|
||||
<div :class="bem('actions')">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</card-component>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" bem-block="QuestionnaireCard">
|
||||
import { ProgressBar } from '@/entities'
|
||||
export type QuestionnaireCardProps = {
|
||||
title: string
|
||||
answers?: number
|
||||
questions: number | string
|
||||
percent?: number
|
||||
assigned?: boolean
|
||||
}
|
||||
|
||||
withDefaults(defineProps<QuestionnaireCardProps>(), {
|
||||
assigned: false,
|
||||
answers: 0,
|
||||
percent: 0,
|
||||
})
|
||||
</script>
|
5
src/entities/patient/ui/QuestionnaireCard/index.ts
Normal file
5
src/entities/patient/ui/QuestionnaireCard/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import QuestionnaireCard, {
|
||||
type QuestionnaireCardProps,
|
||||
} from './QuestionnaireCard.vue'
|
||||
|
||||
export { QuestionnaireCard, type QuestionnaireCardProps }
|
15
src/entities/patient/ui/index.ts
Normal file
15
src/entities/patient/ui/index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export * from './PatientRequest'
|
||||
export * from './PatientNavigation'
|
||||
export * from './ProgressBar'
|
||||
export * from './PatientFilesCard'
|
||||
export * from './PatientHealthMatrix'
|
||||
export * from './PatientBasicInfo'
|
||||
export * from './PatientSurveyCard'
|
||||
export * from './PatientReminders'
|
||||
export * from './EditableCard'
|
||||
export * from './InitialAppointment'
|
||||
export * from './QuestionnaireCard'
|
||||
export * from './EmptySurvey'
|
||||
export * from './InitialHealthMatrix'
|
||||
export * from './InitialPurpose'
|
||||
export * from './EditableInput'
|
16
src/entities/user/api/index.ts
Normal file
16
src/entities/user/api/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type { AxiosResponse } from 'axios'
|
||||
import { baseApi } from '@/shared'
|
||||
import type { User } from '../lib'
|
||||
|
||||
export const fetchUser = (): Promise<UserAPI.GET.FetchUser.Response> =>
|
||||
baseApi.get('user')
|
||||
|
||||
export namespace UserAPI {
|
||||
export namespace GET {
|
||||
export namespace FetchUser {
|
||||
export type Response = AxiosResponse<{
|
||||
data: User
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
4
src/entities/user/index.ts
Normal file
4
src/entities/user/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './model'
|
||||
export * from './lib'
|
||||
export * from './api'
|
||||
export * from './ui'
|
1
src/entities/user/lib/index.ts
Normal file
1
src/entities/user/lib/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './types'
|
24
src/entities/user/lib/types.ts
Normal file
24
src/entities/user/lib/types.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export type User = {
|
||||
id: number
|
||||
name: string
|
||||
email: string
|
||||
birthdate: Maybe<string>
|
||||
city: Maybe<string>
|
||||
marital: Maybe<string>
|
||||
children_count: Maybe<number>
|
||||
profession: Maybe<string>
|
||||
contact: Maybe<string>
|
||||
anamnesis: Maybe<string>
|
||||
asking: Maybe<string>
|
||||
roles: UserRole[]
|
||||
}
|
||||
|
||||
export type UserRole = {
|
||||
id: number
|
||||
name: UserRoles
|
||||
guard_name: number
|
||||
}
|
||||
|
||||
export enum UserRoles {
|
||||
ADMIN = 'admin',
|
||||
}
|
1
src/entities/user/model/index.ts
Normal file
1
src/entities/user/model/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './module'
|
19
src/entities/user/model/module/index.ts
Normal file
19
src/entities/user/model/module/index.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { Stores } from '@/shared'
|
||||
import { fetchUser } from '../../api'
|
||||
import type { User } from '../../lib'
|
||||
|
||||
export const useUserStore = defineStore(Stores.USER, () => {
|
||||
const currentUser = ref<Maybe<User>>(null)
|
||||
|
||||
const setCurrentUser = async () => {
|
||||
const { data } = await fetchUser()
|
||||
currentUser.value = data.data
|
||||
}
|
||||
|
||||
return {
|
||||
currentUser,
|
||||
setCurrentUser,
|
||||
}
|
||||
})
|
17
src/entities/user/ui/UserAvatar/UserAvatar.scss
Normal file
17
src/entities/user/ui/UserAvatar/UserAvatar.scss
Normal file
@ -0,0 +1,17 @@
|
||||
.user-avatar {
|
||||
&__img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
|
||||
&.noavatar {
|
||||
background: var(--purple-8-bg);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
input[type='file'] {
|
||||
display: none;
|
||||
}
|
||||
}
|
59
src/entities/user/ui/UserAvatar/UserAvatar.vue
Normal file
59
src/entities/user/ui/UserAvatar/UserAvatar.vue
Normal file
@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<label :class="bem()">
|
||||
<input
|
||||
type="file"
|
||||
id="upload-avatar"
|
||||
accept="image/*"
|
||||
@change="uploadAvatar"
|
||||
/>
|
||||
<img
|
||||
v-if="avatar"
|
||||
v-lazy="configs.mainHost + avatar"
|
||||
alt="avatar"
|
||||
:class="bem('img')"
|
||||
@click="setAvatar"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
:class="[
|
||||
bem('img'),
|
||||
'noavatar',
|
||||
'row',
|
||||
'justify-center',
|
||||
'items-center',
|
||||
]"
|
||||
@click="setAvatar"
|
||||
>
|
||||
<icon-base-component name="camera" size="xs" view="brand" />
|
||||
</div>
|
||||
</label>
|
||||
</template>
|
||||
<script setup lang="ts" bem-block="UserAvatar">
|
||||
import configs from '@/app/configs'
|
||||
export type UserAvatarProps = {
|
||||
avatar: string // url image
|
||||
userId: number | string
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:avatar', data: FormData): void
|
||||
}>()
|
||||
const props = defineProps<UserAvatarProps>()
|
||||
|
||||
/**------------- Methods --------------- */
|
||||
const setAvatar = () => {
|
||||
const input = document.getElementById('upload-avatar')
|
||||
input?.click()
|
||||
}
|
||||
|
||||
const uploadAvatar = async (e: Event) => {
|
||||
const file = (e.target as HTMLInputElement).files?.[0]
|
||||
|
||||
if (!file) return
|
||||
|
||||
const formdata = new FormData()
|
||||
formdata.append('avatar', file)
|
||||
formdata.append('user_id', String(props.userId))
|
||||
emit('update:avatar', formdata)
|
||||
}
|
||||
</script>
|
3
src/entities/user/ui/UserAvatar/index.ts
Normal file
3
src/entities/user/ui/UserAvatar/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import UserAvatar, { type UserAvatarProps } from './UserAvatar.vue'
|
||||
|
||||
export { UserAvatar, type UserAvatarProps }
|
1
src/entities/user/ui/index.ts
Normal file
1
src/entities/user/ui/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './UserAvatar'
|
Reference in New Issue
Block a user