Files
onboard/resources/js/Pages/Nurse/Report/Index.vue

283 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import AppLayout from "../../../Layouts/AppLayout.vue";
import {NFlex, NTag, NDataTable, NButton, NTabs, NTabPane, NSpace} from 'naive-ui'
import AppContainer from "../../../Components/AppContainer.vue";
import AppPanel from "../../../Components/AppPanel.vue";
import ShiftPickerQuery from "../../../Components/ShiftPickerQuery.vue";
import UrgencyBadge from "../../../Components/UrgencyBadge.vue";
import {computed, h, onMounted, ref, shallowRef} from "vue"
import {TbCirclePlus, TbPencil} from 'vue-icons-plus/tb'
import {useAuthStore} from "../../../Stores/auth.js";
import {usePage} from "@inertiajs/vue3";
import AddMedicalHistoryModal from "../Components/AddMedicalHistoryModal.vue";
import EditMedicalHistoryModal from "../Components/EditMedicalHistoryModal.vue";
import {router} from "@inertiajs/vue3";
import ActionsColumnDataTable from "../Components/ActionsColumnDataTable.vue";
import {useAppDialog} from "../../../Composables/useAppDialog.js";
import {format} from "date-fns";
import DatePickerQuery from "../../../Components/DatePickerQuery.vue";
const props = defineProps({
patients: {
type: Array,
default: []
},
reportNurseId: {
type: Number,
default: null
},
canSaveReport: Boolean,
canEditPastReport: Boolean,
department: {
type: Object,
default: null
},
selectedUserId: {
type: Number,
default: null
},
selectedDepartmentId: {
type: Number,
default: null
},
dates: {
type: Array,
default: []
},
latestReport: {
type: Object,
default: () => ({ })
},
})
const canEdit = computed(() => props.canSaveReport || props.canEditPastReport)
const showAddMedicalHistoryModal = shallowRef(false)
const showEditMedicalHistoryModal = shallowRef(false)
const editHistoryId = ref(null)
const patientSource = ref(null)
const authStore = useAuthStore()
const userDepartment = authStore.userDepartment
const loading = ref(false)
const patientsByGroup = computed(() => {
const groups = {
urgent: [],
planned: [],
deceased: [],
in_department: [],
recipient: [],
discharged: [],
transferred: [],
reanimations: [],
observables: []
}
const patients = props.patients?.data ?? []
for (const raw of patients) {
const p = raw
const flags = p.period_flags ?? {}
const isCurrentAtEnd = flags.current_at_end ?? ['in_department', 'recipient'].includes(p.patient_status)
// Группировка по срочности за период: пациент должен состоять на конец периода.
if (isCurrentAtEnd && (flags.urgent ?? p.patient_urgency === 'urgent')) groups.urgent.push(p)
else if (isCurrentAtEnd && (flags.planned ?? p.patient_urgency === 'planned')) groups.planned.push(p)
// Событийная группировка за период. Один пациент может быть в нескольких группах.
if (flags.recipient ?? p.patient_status === 'recipient') groups.recipient.push(p)
if (flags.current_at_end ?? p.patient_status === 'in_department') groups.in_department.push(p)
if (flags.discharged ?? p.patient_status === 'discharged') groups.discharged.push(p)
if (flags.deceased ?? p.patient_status === 'deceased') groups.deceased.push(p)
if (flags.transferred ?? p.patient_status === 'transferred') groups.transferred.push(p)
}
return groups
})
const columns = [
{
title: 'ФИО',
key: 'full_name',
minWidth: 280,
maxWidth: 400,
resizable: true
},
{
title: 'Дата поступления',
key: 'latest_migration.ingoing_date',
minWidth: 180,
maxWidth: 180,
width: 180,
resizable: false,
render: (row) => format(new Date(row.migrations[0].ingoing_date), 'dd.MM.yyyy HH:mm')
},
{
title: 'Срочность',
key: 'urgency_id',
render: (row) => {
return h(UrgencyBadge, {urgencyId: row.urgency_id})
}
},
{
title: '',
key: 'actions',
align: 'end',
render: (row) => {
return h(
ActionsColumnDataTable,
{
row: row,
canEdit: canEdit.value,
onClickDelete: (historyId) => onClickDeleteButton(historyId),
onClickEdit: (row) => onClickEditButton(row),
}
)
}
}
]
const onClickEditButton = (row) => {
showEditMedicalHistoryModal.value = true
editHistoryId.value = row.id
patientSource.value = row.source
}
const onClickDeleteButton = async (historyId) => {
const confirmed = await useAppDialog({
title: 'Удалить историю?',
content: 'Это действие необратимо',
onConfirm: async () => {
await axios.delete(`/api/nurse/patients/${historyId}`)
}
})
if (confirmed) {
loading.value = true
router.reload({
only: ['patients'],
onSuccess: () => {
loading.value = false
}
})
}
}
const submit = () => {
router.post('/nurse/report/save', {
userId: props.selectedUserId,
departmentId: props.selectedDepartmentId,
}, {
onSuccess: () => {
alert('Сохранено')
}
})
}
const reportCreator = computed(() => {
if (props.latestReport === null) return ''
if (props.latestReport.doctor.LPUDoctorID === 0) return 'Отчет создан системой'
else return `Отчет создан: ${props.latestReport.doctor.FAM_V} ${props.latestReport.doctor.IM_V} ${props.latestReport.doctor.OT_V}`
})
const reportCreatorType = computed(() => {
if (props.latestReport === null) return 'warning'
if (props.latestReport.doctor.LPUDoctorID === 0) return 'error'
else return 'warning'
})
const formattedLabel = (word, count) => {
return `${word} (${count})`
}
</script>
<template>
<AppLayout>
<AppContainer>
<AppPanel>
<NFlex justify="space-between" align="center">
<NSpace>
<NTag type="info" :bordered="false">
{{ department?.name_full ?? userDepartment.name_full }}
</NTag>
<NTag v-if="props.latestReport" :type="reportCreatorType" :bordered="false">
{{ reportCreator }}
</NTag>
</NSpace>
<DatePickerQuery :date="dates" :is-head-or-admin="true" class="text-lg!" hint />
</NFlex>
</AppPanel>
<AppPanel header="Журнал пациентов" header-include-body>
<template v-if="canEdit" #header-extra>
<NButton secondary :loading="loading" @click="showAddMedicalHistoryModal = true">
<template #icon>
<TbCirclePlus />
</template>
Добавить пациента
</NButton>
</template>
<NTabs type="line">
<NTabPane name="all"
:tab="formattedLabel('В отделении', patientsByGroup.in_department.length)"
>
<NDataTable :columns="columns"
:data="patientsByGroup.in_department"
table-layout="fixed"
max-height="calc(100vh - 435px)"
min-height="calc(100vh - 435px)"
:loading="loading"
/>
</NTabPane>
<NTabPane name="income" :tab="formattedLabel('Поступившие', patientsByGroup.recipient.length)">
<NDataTable :columns="columns"
:data="patientsByGroup.recipient"
table-layout="fixed"
max-height="calc(100vh - 435px)"
min-height="calc(100vh - 435px)"
:loading="loading"
/>
</NTabPane>
<NTabPane name="outcome" :tab="formattedLabel('Выписанные', patientsByGroup.discharged.length)">
<NDataTable :columns="columns"
:data="patientsByGroup.discharged"
table-layout="fixed"
max-height="calc(100vh - 435px)"
min-height="calc(100vh - 435px)"
:loading="loading"
/>
</NTabPane>
<NTabPane name="dead" :tab="formattedLabel('Умершие', patientsByGroup.deceased.length)">
<NDataTable :columns="columns"
:data="patientsByGroup.deceased"
table-layout="fixed"
max-height="calc(100vh - 435px)"
min-height="calc(100vh - 435px)"
:loading="loading"
/>
</NTabPane>
<NTabPane name="transfer" :tab="formattedLabel('Переведенные', patientsByGroup.transferred.length)">
<NDataTable :columns="columns"
:data="patientsByGroup.transferred"
table-layout="fixed"
max-height="calc(100vh - 435px)"
min-height="calc(100vh - 435px)"
:loading="loading"
/>
</NTabPane>
</NTabs>
</AppPanel>
<NButton v-if="canSaveReport" secondary size="large" @click="submit" :loading="loading">
Сохранить введенные данные
</NButton>
</AppContainer>
<AddMedicalHistoryModal v-model:show="showAddMedicalHistoryModal" />
<EditMedicalHistoryModal v-model:show="showEditMedicalHistoryModal" :history-id="editHistoryId" :patient-source="patientSource" :report-nurse-id="props.reportNurseId" />
</AppLayout>
</template>
<style scoped>
:deep(.n-data-table-th),
:deep(.n-data-table-td) {
font-size: var(--n-font-size);
}
</style>