Обновлен стартовый экран
Переписаны запросы для статистики, отчетов Добавлена интеграция отчета сестры
This commit is contained in:
@@ -1,35 +1,29 @@
|
||||
<script setup>
|
||||
import AppLayout from "../../Layouts/AppLayout.vue";
|
||||
import {useReportStore} from "../../Stores/report.js";
|
||||
import {computed, h, onMounted, ref, watch} from "vue";
|
||||
import {onMounted, ref, watch, provide} from "vue";
|
||||
import {useAuthStore} from "../../Stores/auth.js";
|
||||
import {
|
||||
NFormItem,
|
||||
NFlex,
|
||||
NInputNumber,
|
||||
NStatistic,
|
||||
NTabPane,
|
||||
NTabs,
|
||||
NTag,
|
||||
NNumberAnimation,
|
||||
NSkeleton,
|
||||
NRow, NCol, NDivider, NButton
|
||||
NRow, NCol, NDivider, NButton, NSpace
|
||||
} from "naive-ui";
|
||||
import DatePickerQuery from "../../Components/DatePickerQuery.vue";
|
||||
import AppPanel from "../../Components/AppPanel.vue";
|
||||
import AppContainer from "../../Components/AppContainer.vue";
|
||||
import PatientTypeSection from "./Components/PatientSection.vue";
|
||||
import PatientTypeSectionItem from "./Components/PatientSectionItem.vue";
|
||||
import PatientDataTable from "./Components/PatientDataTable.vue";
|
||||
import {format, formatDistanceStrict} from 'date-fns';
|
||||
import { ru } from 'date-fns/locale';
|
||||
import TooltipColumn from "./Components/DataTableColumns/TooltipColumn.vue";
|
||||
import ActionsColumn from "./Components/DataTableColumns/ActionsColumn.vue";
|
||||
import OperationsColumn from "./Components/DataTableColumns/OperationsColumn.vue";
|
||||
import ReportWidget from "./Components/ReportWidget.vue";
|
||||
import OperationInfoModal from "./Components/OperationInfoModal.vue";
|
||||
import {usePatientColumns} from "../../Composables/usePatientColumns.js";
|
||||
import {router} from "@inertiajs/vue3";
|
||||
import HeaderWidget from "./Components/HeaderWidget.vue";
|
||||
import DutyPatientsPane from "./Components/DutyPatientsPane.vue";
|
||||
import NursePatientsPane from "./Components/NursePatientsPane.vue";
|
||||
import OperationWidget from "./Components/Widgets/OperationWidget.vue";
|
||||
import LoadedWidget from "./Components/Widgets/LoadedWidget.vue";
|
||||
import UnwantedEventModal from "./Components/Modals/UnwantedEventModal.vue";
|
||||
import RecipientWidget from "./Components/Widgets/RecipientWidget.vue";
|
||||
|
||||
const props = defineProps({
|
||||
department: {
|
||||
@@ -44,75 +38,154 @@ const props = defineProps({
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
|
||||
canSaveReport: Boolean,
|
||||
canEditPastReport: Boolean,
|
||||
canSaveNurseReport: Boolean,
|
||||
stats: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
duty: {
|
||||
beds: 0,
|
||||
loaded: 0,
|
||||
current: 0,
|
||||
urgent: 0,
|
||||
discharged: 0,
|
||||
},
|
||||
nurse: {
|
||||
current: 0,
|
||||
urgent: 0,
|
||||
discharged: 0,
|
||||
},
|
||||
})
|
||||
},
|
||||
latestReport: {
|
||||
type: Object,
|
||||
default: () => ({ })
|
||||
},
|
||||
patients: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
},
|
||||
nursePatients: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
},
|
||||
dates: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
selectedUserId: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
selectedDepartmentId: {
|
||||
type: Number,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
const reportStore = useReportStore()
|
||||
const authStore = useAuthStore()
|
||||
const userDepartment = authStore.userDepartment
|
||||
const loading = ref(false)
|
||||
const operationsInModal = ref(null)
|
||||
const showOperationsModal = ref(false)
|
||||
|
||||
const patientsByGroup = computed(() => {
|
||||
const groups = {
|
||||
urgent: [],
|
||||
planned: [],
|
||||
deceased: [],
|
||||
in_department: [],
|
||||
recipient: [],
|
||||
discharged: [],
|
||||
transferred: [],
|
||||
};
|
||||
|
||||
for (const p of props.patients.data) {
|
||||
// Группировка по срочности
|
||||
if (p.patient_urgency === 'urgent') groups.urgent.push(p);
|
||||
else if (p.patient_urgency === 'planned') groups.planned.push(p);
|
||||
|
||||
// Группировка по статусу (дублирование нужно, если один пациент может быть в двух таблицах)
|
||||
if (groups.hasOwnProperty(p.patient_status)) {
|
||||
groups[p.patient_status].push(p);
|
||||
}
|
||||
}
|
||||
|
||||
return groups;
|
||||
const patientCollection = ref({...props.patients})
|
||||
const nursePatientCollection = ref({...props.nursePatients})
|
||||
const latestReportObj = ref(props.latestReport ?? {
|
||||
unwanted_events: []
|
||||
})
|
||||
|
||||
const {
|
||||
planOrEmergencyColumns, observableColumns, reanimationColumns, dischargedColumns,
|
||||
deceasedColumns, transferredColumns,
|
||||
} = usePatientColumns({
|
||||
onAddObservable: (row) => {
|
||||
console.log(row)
|
||||
},
|
||||
onShowOperationModal: (operations) => {
|
||||
operationsInModal.value = operations
|
||||
showOperationsModal.value = true
|
||||
const loading = ref(false)
|
||||
const showUnwantedEventModal = ref(false)
|
||||
const reportForm = ref({
|
||||
recipient: props.stats.duty.recipient,
|
||||
discharged: props.stats.duty.discharged,
|
||||
current: props.stats.duty.current,
|
||||
staff: 0,
|
||||
observables: []
|
||||
})
|
||||
const updateReportForm = (form) => {
|
||||
reportForm.value = {
|
||||
...reportForm.value,
|
||||
...form
|
||||
}
|
||||
}
|
||||
const updateUnwantedEvents = (unwanted_events) => {
|
||||
reportForm.value = {
|
||||
...reportForm.value,
|
||||
unwanted_events
|
||||
}
|
||||
|
||||
latestReportObj.value = {
|
||||
...latestReportObj.value,
|
||||
unwanted_events
|
||||
}
|
||||
}
|
||||
|
||||
// Прокидываем reportForm всем потомкам
|
||||
provide('reportForm', {
|
||||
reportForm,
|
||||
updateReportForm
|
||||
})
|
||||
|
||||
const submit = () => {
|
||||
router.post('/duty/report/save', {}, {
|
||||
const queryString = window.location.search;
|
||||
const params = new URLSearchParams(queryString);
|
||||
const url = new URL(`${window.location.origin}/duty/report/save`)
|
||||
const startAt = params.get('startAt')
|
||||
const endAt = params.get('endAt')
|
||||
if (startAt && startAt !== 'null') url.searchParams.append('startAt', startAt)
|
||||
if (endAt && endAt !== 'null') url.searchParams.append('endAt', endAt)
|
||||
|
||||
router.post(url.toString(), {
|
||||
...reportForm.value,
|
||||
userId: props.selectedUserId,
|
||||
departmentId: props.selectedDepartmentId,
|
||||
}, {
|
||||
onSuccess: () => {
|
||||
alert('Сохранено')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const syncPageProps = () => reportStore.initializeFromPage(props)
|
||||
const onShowUnwantedEventModal = () => {
|
||||
showUnwantedEventModal.value = true
|
||||
}
|
||||
|
||||
const addObservablePatient = (observable) => {
|
||||
const collection = patientCollection.value.data
|
||||
const item = collection.find(obs => obs.id === observable.id)
|
||||
if (item) {
|
||||
item.in_observable = true
|
||||
item.observable = { observable_reason: observable.observable_reason }
|
||||
}
|
||||
}
|
||||
|
||||
const removeObservablePatient = (patient) => {
|
||||
const collection = patientCollection.value.data
|
||||
const item = collection.find(p => p.id === patient.id)
|
||||
if (item) {
|
||||
item.in_observable = false
|
||||
}
|
||||
}
|
||||
|
||||
const syncPageProps = (pageProps = props) => {
|
||||
reportStore.initializeFromPage(pageProps)
|
||||
|
||||
patientCollection.value = {...pageProps.patients}
|
||||
nursePatientCollection.value = {...pageProps.nursePatients}
|
||||
latestReportObj.value = pageProps.latestReport ?? { unwanted_events: [] }
|
||||
reportForm.value = {
|
||||
...reportForm.value,
|
||||
recipient: pageProps.stats.duty.recipient,
|
||||
discharged: pageProps.stats.duty.discharged,
|
||||
current: pageProps.stats.duty.current,
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(syncPageProps)
|
||||
|
||||
watch(() => props, (newProps) => {
|
||||
reportStore.initializeFromPage(newProps)
|
||||
syncPageProps(newProps)
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
@@ -129,168 +202,100 @@ watch(() => props, (newProps) => {
|
||||
<AppContainer>
|
||||
<AppPanel>
|
||||
<NFlex justify="space-between" align="center">
|
||||
<NTag type="info" :bordered="false">
|
||||
{{ userDepartment.name_full }}
|
||||
</NTag>
|
||||
<NSpace>
|
||||
<NTag type="info" :bordered="false">
|
||||
{{ department.name_full }}
|
||||
</NTag>
|
||||
<NTag v-if="props.latestReport" type="warning" :bordered="false">
|
||||
Отчет создан: {{ `${props.latestReport.doctor.FAM_V} ${props.latestReport.doctor.IM_V} ${props.latestReport.doctor.OT_V}` }}
|
||||
</NTag>
|
||||
</NSpace>
|
||||
<DatePickerQuery :date="dates" :is-head-or-admin="true" class="text-lg!" />
|
||||
</NFlex>
|
||||
<NDivider dashed class="my-4! mt-3!" />
|
||||
<NRow class="grow space-x-2">
|
||||
<NRow class="grow space-x-2 h-[82.78px]">
|
||||
<NCol :span="3">
|
||||
<AppPanel no-padding>
|
||||
<div class="flex flex-col items-center justify-center text-center py-2">
|
||||
<NStatistic label="Коек">
|
||||
<template #default>
|
||||
<NSkeleton v-if="reportStore.isLoadReportInfo" round class="w-[70px]! mt-2 h-[29px]!" />
|
||||
<NNumberAnimation v-else :from="0" :to="reportStore.reportInfo?.department?.beds" />
|
||||
</template>
|
||||
</NStatistic>
|
||||
</div>
|
||||
</AppPanel>
|
||||
<HeaderWidget label="Коек"
|
||||
:counter="stats.duty.beds" />
|
||||
</NCol>
|
||||
<NCol :span="3">
|
||||
<AppPanel no-padding>
|
||||
<div class="flex flex-col items-center justify-center text-center py-2">
|
||||
<NStatistic label="Загруженность">
|
||||
<template #default>
|
||||
<NNumberAnimation :from="0" :to="reportStore.reportInfo?.department?.percentLoadedBeds" />
|
||||
<span>%</span>
|
||||
</template>
|
||||
</NStatistic>
|
||||
</div>
|
||||
</AppPanel>
|
||||
<LoadedWidget :counter="stats.duty.loaded" />
|
||||
</NCol>
|
||||
<NCol :span="3">
|
||||
<AppPanel no-padding>
|
||||
<div class="flex flex-col items-center justify-center text-center py-2">
|
||||
<NStatistic label="Состоит">
|
||||
<NNumberAnimation :from="0" :to="patientsByGroup.in_department.length + patientsByGroup.recipient.length" />
|
||||
</NStatistic>
|
||||
</div>
|
||||
</AppPanel>
|
||||
<RecipientWidget :beds="stats.duty.beds"
|
||||
:duty-current="stats.duty.current"
|
||||
:nurse-current="stats.nurse.current"
|
||||
/>
|
||||
</NCol>
|
||||
<NCol :span="3">
|
||||
<AppPanel no-padding>
|
||||
<div class="flex flex-col items-center justify-center text-center py-2">
|
||||
<NStatistic label="Поступило">
|
||||
<NNumberAnimation :from="0" :to="patientsByGroup.recipient.length" />
|
||||
</NStatistic>
|
||||
</div>
|
||||
</AppPanel>
|
||||
<HeaderWidget label="Поступило"
|
||||
is-double-counter
|
||||
:counter="stats.duty.recipient"
|
||||
:counter-suffix="stats.nurse.recipient"
|
||||
/>
|
||||
</NCol>
|
||||
<NCol :span="3">
|
||||
<AppPanel no-padding>
|
||||
<div class="flex flex-col items-center justify-center text-center py-2">
|
||||
<NStatistic label="Выбыло">
|
||||
<NNumberAnimation :from="0" :to="patientsByGroup.deceased.length + patientsByGroup.discharged.length" />
|
||||
</NStatistic>
|
||||
</div>
|
||||
</AppPanel>
|
||||
<HeaderWidget label="Выбыло"
|
||||
is-double-counter
|
||||
:counter="stats.duty.discharged"
|
||||
:counter-suffix="stats.nurse.discharged"
|
||||
/>
|
||||
</NCol>
|
||||
</NRow>
|
||||
|
||||
</AppPanel>
|
||||
<div class="grid grid-cols-[1fr_auto] gap-x-2">
|
||||
<AppPanel>
|
||||
<NFlex align="center" :wrap="false">
|
||||
<NFormItem label="Поступило" :show-feedback="false">
|
||||
<NInputNumber :min="0" />
|
||||
<NInputNumber :disabled="!canSaveReport" :min="0" v-model:value="reportForm.recipient" />
|
||||
</NFormItem>
|
||||
<NFormItem label="Выбыло" :show-feedback="false">
|
||||
<NInputNumber :min="0" />
|
||||
<NInputNumber :disabled="!canSaveReport" :min="0" v-model:value="reportForm.discharged" />
|
||||
</NFormItem>
|
||||
<NFormItem label="Состоит" :show-feedback="false">
|
||||
<NInputNumber :min="0" />
|
||||
<NInputNumber :disabled="!canSaveReport" :min="0" v-model:value="reportForm.current" />
|
||||
</NFormItem>
|
||||
<NFormItem label="Мед. персонал" :show-feedback="false">
|
||||
<NInputNumber :min="0" />
|
||||
<NInputNumber :disabled="!canSaveReport" :min="0" v-model:value="reportForm.staff" />
|
||||
</NFormItem>
|
||||
</NFlex>
|
||||
</AppPanel>
|
||||
<NFlex :wrap="false" :size="8">
|
||||
<ReportWidget :to="patientsByGroup.deceased.length">
|
||||
<ReportWidget :counter="stats.duty.deceased">
|
||||
Умерло
|
||||
</ReportWidget>
|
||||
<ReportWidget :to="patientsByGroup.deceased.length">
|
||||
<NSpace vertical :size="0">
|
||||
<ReportWidget :counter="stats.duty.deceased">
|
||||
<NSpace vertical :size="1">
|
||||
<div>Летальность</div>
|
||||
<div>%</div>
|
||||
</NSpace>
|
||||
</ReportWidget>
|
||||
<ReportWidget :to="patientsByGroup.deceased.length">
|
||||
<NSpace vertical :size="0">
|
||||
<div>Операций</div>
|
||||
<div>Э / П</div>
|
||||
</NSpace>
|
||||
</ReportWidget>
|
||||
<OperationWidget :planned="stats.duty.surgical_planned" :urgent="stats.duty.surgical_urgent" />
|
||||
</NFlex>
|
||||
</div>
|
||||
<AppPanel>
|
||||
<NTabs type="segment">
|
||||
<NTabPane name="mis" tab="МИС">
|
||||
<PatientTypeSection>
|
||||
<PatientTypeSectionItem label="Планово" :counter="patientsByGroup.planned.length">
|
||||
<PatientDataTable :data="patientsByGroup.planned" :columns="planOrEmergencyColumns" />
|
||||
</PatientTypeSectionItem>
|
||||
<PatientTypeSectionItem label="Экстренно" :counter="patientsByGroup.urgent.length">
|
||||
<PatientDataTable :data="patientsByGroup.urgent" :columns="planOrEmergencyColumns" />
|
||||
</PatientTypeSectionItem>
|
||||
<PatientTypeSectionItem label="Находятся на контроле" :counter="patientsByGroup.recipient.length">
|
||||
<PatientDataTable :data="patientsByGroup.recipient" :columns="observableColumns" />
|
||||
</PatientTypeSectionItem>
|
||||
<PatientTypeSectionItem label="Находятся в реанимации" :counter="patientsByGroup.recipient.length">
|
||||
<PatientDataTable :data="patientsByGroup.recipient" :columns="reanimationColumns" />
|
||||
</PatientTypeSectionItem>
|
||||
<PatientTypeSectionItem label="Выбывшие" :counter="patientsByGroup.discharged.length + patientsByGroup.deceased.length">
|
||||
<NTabs type="segment" animated>
|
||||
<NTabPane name="1" :tab="`Выписанные (${patientsByGroup.discharged.length})`">
|
||||
<PatientDataTable :data="patientsByGroup.discharged" :columns="dischargedColumns" />
|
||||
</NTabPane>
|
||||
<NTabPane name="2" :tab="`Умершие (${patientsByGroup.deceased.length})`">
|
||||
<PatientDataTable :data="patientsByGroup.deceased" :columns="deceasedColumns" />
|
||||
</NTabPane>
|
||||
<NTabPane name="3" :tab="`Переведенные (${patientsByGroup.transferred.length})`">
|
||||
<PatientDataTable :data="patientsByGroup.transferred" :columns="transferredColumns" />
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
</PatientTypeSectionItem>
|
||||
</PatientTypeSection>
|
||||
</NTabPane>
|
||||
<NTabPane name="nurse" tab="Мед. сестра">
|
||||
<PatientTypeSection>
|
||||
<PatientTypeSectionItem label="Планово" :counter="patientsByGroup.planned.length">
|
||||
<PatientDataTable :data="patientsByGroup.planned" :columns="planOrEmergencyColumns" />
|
||||
</PatientTypeSectionItem>
|
||||
<PatientTypeSectionItem label="Экстренно" :counter="patientsByGroup.urgent.length">
|
||||
<PatientDataTable :data="patientsByGroup.urgent" :columns="planOrEmergencyColumns" />
|
||||
</PatientTypeSectionItem>
|
||||
<PatientTypeSectionItem label="Находятся на контроле" :counter="patientsByGroup.recipient.length">
|
||||
<PatientDataTable :data="patientsByGroup.recipient" :columns="observableColumns" />
|
||||
</PatientTypeSectionItem>
|
||||
<PatientTypeSectionItem label="Находятся в реанимации" :counter="patientsByGroup.recipient.length">
|
||||
<PatientDataTable :data="patientsByGroup.recipient" :columns="reanimationColumns" />
|
||||
</PatientTypeSectionItem>
|
||||
<PatientTypeSectionItem label="Выбывшие" :counter="patientsByGroup.discharged.length + patientsByGroup.deceased.length">
|
||||
<NTabs type="segment" animated>
|
||||
<NTabPane name="1" :tab="`Выписанные (${patientsByGroup.discharged.length})`">
|
||||
<PatientDataTable :data="patientsByGroup.discharged" :columns="dischargedColumns" />
|
||||
</NTabPane>
|
||||
<NTabPane name="2" :tab="`Умершие (${patientsByGroup.deceased.length})`">
|
||||
<PatientDataTable :data="patientsByGroup.deceased" :columns="deceasedColumns" />
|
||||
</NTabPane>
|
||||
<NTabPane name="3" :tab="`Переведенные (${patientsByGroup.transferred.length})`">
|
||||
<PatientDataTable :data="patientsByGroup.transferred" :columns="transferredColumns" />
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
</PatientTypeSectionItem>
|
||||
</PatientTypeSection>
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
<NTabs type="segment">
|
||||
<NTabPane name="mis" tab="МИС" display-directive="show">
|
||||
<DutyPatientsPane :patients="patientCollection" :nurse-patients="nursePatientCollection" @create-observable="addObservablePatient" @remove-observable="removeObservablePatient" />
|
||||
</NTabPane>
|
||||
<NTabPane name="nurse" tab="Журнал пациентов" display-directive="show">
|
||||
<NursePatientsPane :patients="nursePatientCollection" />
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
</AppPanel>
|
||||
<AppPanel>
|
||||
<NFlex justify="space-between">
|
||||
<NButton v-if="props.canSaveReport || props.canEditPastReport"
|
||||
secondary type="error" :loading="loading" @click="onShowUnwantedEventModal">
|
||||
Нежелательные события ({{ latestReportObj.unwanted_events?.length ?? 0 }})
|
||||
</NButton>
|
||||
<NButton v-if="props.canSaveReport" secondary @click="submit" :loading="loading">
|
||||
Сохранить отчет
|
||||
</NButton>
|
||||
</NFlex>
|
||||
</AppPanel>
|
||||
<NButton secondary size="large" @click="submit" :loading="loading">
|
||||
Сохранить отчет
|
||||
</NButton>
|
||||
</AppContainer>
|
||||
|
||||
<UnwantedEventModal v-model:show="showUnwantedEventModal" :can-save-report="props.canSaveReport" :report="latestReportObj" @onSubmit="updateUnwantedEvents" />
|
||||
</AppLayout>
|
||||
<OperationInfoModal :operations="operationsInModal" v-model:show="showOperationsModal" />
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user