* исправление подсчета операций пациентов

* поправил поле выбора даты
* добавил индикатор в контроле
* окно выбора пользователя для сводной
* привязка окна для ввода причины контроля
* добавил привязку историй пациентов для просмотра статистики по дням
* поправил фиксацию фио ответственного, убрал при диапазоне
* отключение ролей адм и зав от реплики
This commit is contained in:
brusnitsyn
2026-01-30 17:26:16 +09:00
parent 87e21f0e08
commit 2805e5e4bc
21 changed files with 836 additions and 156 deletions

View File

@@ -116,6 +116,12 @@ const modelComputed = computed({
reportStore.timestampCurrentRange = [value, value]
}
const queryString = window.location.search
const params = new URLSearchParams(queryString)
const userId = params.get('userId')
reportStore.reportInfo.userId = userId
reportStore.getDataOnReportDate(reportStore.timestampCurrentRange)
}
})

View File

@@ -19,8 +19,16 @@ const props = defineProps({
icon: {
type: [Object, Function],
default: null
},
tag: {
type: [Object, Function, String],
default: Link
},
click: {
type: [Function, Object],
}
})
const emits = defineEmits(['click'])
const buttonThemeOverride = {
heightLarge: '64px',
@@ -33,10 +41,25 @@ const pThemeOverride = {
}
const hasIcon = computed(() => props.icon !== null)
const isLink = computed(() => props.tag === 'link')
const onClick = () => {
if (isLink.value) return
emits('click')
}
</script>
<template>
<NButton :tag="Link" :href="href" :theme-overrides="buttonThemeOverride" size="large" block class="justify-start! text-left!">
<NButton :tag="tag"
:href="href"
:theme-overrides="buttonThemeOverride"
@click="onClick"
size="large"
block
class="justify-start! text-left!"
>
<template v-if="hasIcon" #icon>
<component :is="icon" v-if="icon" />
</template>

View File

@@ -18,7 +18,6 @@ const formRole = useForm({
role_id: authStore.user.role.role_id
})
const onChangeRole = (roleId) => {
console.log(roleId)
formRole.post('/user/role/change', {
onSuccess: () => {
router.visit(window.location.pathname, {

View File

@@ -3,12 +3,14 @@ import AppLayout from "../Layouts/AppLayout.vue";
import {useAuthStore} from "../Stores/auth.js";
import {NH1, NSpace, NP, NFlex} from 'naive-ui'
import StartButton from "../Components/StartButton.vue";
import {computed} from "vue";
import {computed, ref} from "vue";
import {format} from "date-fns";
import {ru} from "date-fns/locale";
import {useNow} from "@vueuse/core";
import {TbArticle, TbChartTreemap, TbDoorExit} from "vue-icons-plus/tb";
import {useReportStore} from "../Stores/report.js";
import SelectUserModal from "./Report/Components/SelectUserModal.vue";
import {Link} from "@inertiajs/vue3";
const authStore = useAuthStore()
const reportStore = useReportStore()
@@ -20,6 +22,14 @@ const currentDate = computed(() => {
return formatted.charAt(0).toUpperCase() + formatted.slice(1)
})
const showSelectUserModal = ref(false)
const onShowSelectUserModal = () => {
if (authStore.isDoctor)
showSelectUserModal.value = true
}
const reportButtonType = computed(() => authStore.isDoctor ? 'button' : Link)
</script>
<template>
@@ -38,6 +48,8 @@ const currentDate = computed(() => {
<StartButton title="Заполнить сводную"
description="Заполняется регулярно"
href="/report"
:tag="reportButtonType"
@click="onShowSelectUserModal"
:icon="TbArticle"
/>
<StartButton title="Статистика моего отделения"
@@ -52,6 +64,7 @@ const currentDate = computed(() => {
/>
</NFlex>
</div>
<SelectUserModal v-model:show="showSelectUserModal" />
</AppLayout>
</template>

View File

@@ -34,7 +34,7 @@ const onSubmit = () => {
<ReportSection label="Планово" />
<NButton v-if="reportStore.reportInfo?.report.isActiveSendButton" secondary size="large" @click="onSubmit">
<NButton v-if="reportStore.reportInfo?.report?.isActiveSendButton" secondary size="large" @click="onSubmit">
Сохранить отчет
</NButton>
</NFlex>

View File

@@ -9,7 +9,7 @@ const authStore = useAuthStore()
<template>
<div class="grid grid-cols-[1fr_auto] gap-x-3">
<NCard v-if="reportStore.reportInfo?.report.isActiveSendButton">
<NCard v-if="reportStore.reportInfo?.report?.isActiveSendButton">
<NForm>
<template v-if="reportStore.isLoadReportInfo">
<NFlex>
@@ -35,13 +35,13 @@ const authStore = useAuthStore()
style="--n-padding-top: 0; --n-padding-left: 0; --n-padding-bottom: 0; --n-padding-right: 0;"
>
<div class="w-full h-full flex flex items-center justify-center">
<NStatistic label="Умерло" :value="reportStore.reportInfo?.department.deadCount" />
<NStatistic label="Умерло" :value="reportStore.reportInfo?.department?.deadCount" />
</div>
</NCard>
<NCard class="min-w-[120px] max-w-[120px] min-h-[100px] max-h-[102px] h-full"
style="--n-padding-top: 0; --n-padding-bottom: 0; --n-padding-left: 8px; --n-padding-right: 8px;">
<div class="w-full h-full flex flex items-center justify-center">
<NStatistic :value="reportStore.reportInfo?.department.surgicalCount[1]">
<NStatistic :value="reportStore.reportInfo?.department?.surgicalCount[1]">
<template #label>
<div class="flex flex-col">
<span>Операций</span>
@@ -49,7 +49,7 @@ const authStore = useAuthStore()
</div>
</template>
<template #suffix>
/ {{ reportStore.reportInfo?.department.surgicalCount[0] }}
/ {{ reportStore.reportInfo?.department?.surgicalCount[0] }}
</template>
</NStatistic>
</div>

View File

@@ -1,5 +1,5 @@
<script setup>
import {NSkeleton, NStatistic, NRow, NCol, NCard, NButton, NTag, NDatePicker, NFlex, NSelect, NText, NH2} from "naive-ui";
import {NSkeleton, NStatistic, NRow, NCol, NSpace, NCard, NButton, NTag, NDatePicker, NFlex, NSelect, NText, NH2} from "naive-ui";
import {computed, ref} from "vue";
import {format} from "date-fns";
import {useNow} from "@vueuse/core";
@@ -55,10 +55,15 @@ const currentDate = computed(() => {
<NCard>
<NFlex vertical>
<div class="grid grid-cols-[auto_1fr_auto] items-center">
<NTag v-if="isFillableMode" type="info" :bordered="false">
{{ authStore.userDepartment.name_full }}
</NTag>
<DepartmentSelect v-if="isReadonlyMode" />
<NSpace align="center">
<NTag v-if="isFillableMode" type="info" :bordered="false">
{{ authStore.userDepartment.name_full }}
</NTag>
<DepartmentSelect v-if="isReadonlyMode" />
<NTag v-if="reportStore.reportInfo?.userName" type="warning">
Ответственный: {{ reportStore.reportInfo?.userName }}
</NTag>
</NSpace>
<div class="col-3 w-full">
<ReportSelectDate />
@@ -71,7 +76,7 @@ const currentDate = computed(() => {
<NStatistic label="Коек">
<template #default>
<NSkeleton v-if="reportStore.isLoadReportInfo" round class="w-[70px]! mt-2 h-[29px]!" />
<span v-else>{{ reportStore.reportInfo?.department.beds }}</span>
<span v-else>{{ reportStore.reportInfo?.department?.beds }}</span>
</template>
</NStatistic>
</NCol>
@@ -79,7 +84,7 @@ const currentDate = computed(() => {
<NStatistic label="Загруженность">
<template #default>
<NSkeleton v-if="reportStore.isLoadReportInfo" round class="w-[70px]! mt-2 h-[29px]!" />
<span v-else>{{ reportStore.reportInfo?.department.percentLoadedBeds }}%</span>
<span v-else>{{ reportStore.reportInfo?.department?.percentLoadedBeds }}%</span>
</template>
</NStatistic>
</NCol>
@@ -87,7 +92,7 @@ const currentDate = computed(() => {
<NStatistic label="Поступило">
<template #default>
<NSkeleton v-if="reportStore.isLoadReportInfo" round class="w-[70px]! mt-2 h-[29px]!" />
<span v-else>{{ reportStore.reportInfo?.department.recipientCount }}</span>
<span v-else>{{ reportStore.reportInfo?.department?.recipientCount }}</span>
</template>
</NStatistic>
</NCol>
@@ -95,7 +100,7 @@ const currentDate = computed(() => {
<NStatistic label="Выбыло">
<template #default>
<NSkeleton v-if="reportStore.isLoadReportInfo" round class="w-[70px]! mt-2 h-[29px]!" />
<span v-else>{{ reportStore.reportInfo?.department.extractCount }}</span>
<span v-else>{{ reportStore.reportInfo?.department?.extractCount }}</span>
</template>
</NStatistic>
</NCol>
@@ -103,7 +108,7 @@ const currentDate = computed(() => {
<NStatistic label="Состоит">
<template #default>
<NSkeleton v-if="reportStore.isLoadReportInfo" round class="w-[70px]! mt-2 h-[29px]!" />
<span v-else>{{ reportStore.reportInfo?.department.currentCount }}</span>
<span v-else>{{ reportStore.reportInfo?.department?.currentCount }}</span>
</template>
</NStatistic>
</NCol>

View File

@@ -47,7 +47,7 @@ const isReadonlyMode = computed(() => props.mode.toLowerCase() === 'readonly')
<ReportSectionHeader title="Планово" status="plan" />
</template>
<ReportSectionItem status="plan"
:accent-ids="reportStore.reportInfo?.department.recipientIds"
:accent-ids="reportStore.reportInfo?.department?.recipientIds"
is-draggable
@item-dragged="handleItemDragged"
/>
@@ -57,7 +57,7 @@ const isReadonlyMode = computed(() => props.mode.toLowerCase() === 'readonly')
<ReportSectionHeader title="Экстренно" status="emergency" />
</template>
<ReportSectionItem status="emergency"
:accent-ids="reportStore.reportInfo?.department.recipientIds"
:accent-ids="reportStore.reportInfo?.department?.recipientIds"
is-draggable
@item-dragged="handleItemDragged"
/>

View File

@@ -1,10 +1,10 @@
<script setup>
import {NIcon, NText, NDataTable, NButton, NTabs, NTabPane} from "naive-ui";
import {NIcon, NText, NDataTable, NButton, NBadge, NTabs, NTabPane} from "naive-ui";
import {useReportStore} from "../../../Stores/report.js";
import {computed, h, onMounted, ref, watch} from "vue";
import { VueDraggableNext } from 'vue-draggable-next'
import {storeToRefs} from "pinia";
import {TbGripVertical} from "vue-icons-plus/tb";
import {TbGripVertical, TbEye} from "vue-icons-plus/tb";
import MoveModalComment from "./MoveModalComment.vue";
const props = defineProps({
@@ -49,6 +49,7 @@ const data = ref([])
const isLoading = ref(true)
const showMoveModal = ref(false)
const latestDropItem = ref(null)
const activePatient = ref(null)
// Добавляем drag колонку если режим fillable
const columns = computed(() => {
@@ -100,22 +101,40 @@ const columns = computed(() => {
}
const expandColumn = {
type: 'expand',
renderExpand: (rowData) => {
title: '',
width: '30',
render: (rowData) => {
return h(
NText,
NIcon,
{
class: 'max-w-full break-words whitespace-normal'
onClick: () => {
latestDropItem.value = rowData
showMoveModal.value = true
}
},
{
default: rowData.comment ?? 'Причина наблюдения не указана'
default: h(TbEye)
}
)
}
}
const fillableColumn = {
title: '',
key: 'fillable',
width: '20',
render: (row) => h(
NBadge,
{
dot: true,
color: (row.comment && row.comment.trim()) ? '#7fe7c4' : '#e88080'
}
)
}
if (props.status === 'observation') {
newColumns.push(expandColumn)
newColumns.push(fillableColumn)
}
if (props.isDraggable) newColumns.push(dragColumn)

View File

@@ -0,0 +1,82 @@
<script setup>
import { NSelect, NModal, NForm, NFormItem, NButton } from 'naive-ui'
import {useReportStore} from "../../../Stores/report.js";
import {computed, onMounted, ref} from "vue";
import {router} from "@inertiajs/vue3";
const show = defineModel('show')
const reportStore = useReportStore()
const formRef = ref()
const users = computed(() => reportStore.departmentUsers.map(itm => ({
label: `${itm.FAM_V} ${itm.IM_V} ${itm.OT_V}`,
value: itm.LPUDoctorID
})))
const fetchUsers = () => {
axios.get('/api/mis/department-users')
.then((res) => {
reportStore.departmentUsers = res.data
})
}
const rules = {
userId: {
required: true,
validator: (rule, value) => {
if (Number.isInteger(value)) return true
return false
},
trigger: ['change', 'blur'],
message: 'Выберите ответственного'
}
}
onMounted(() => {
reportStore.reportInfo.userId = null
fetchUsers()
})
const onSubmit = (e) => {
e.preventDefault()
formRef.value?.validate((errors) => {
if (!errors) {
router.visit(`/report?userId=${reportStore.reportInfo.userId}`)
}
else {
}
})
}
const onAfterLeave = () => {
reportStore.reportInfo.userId = null
}
</script>
<template>
<NModal v-model:show="show"
preset="card"
class="max-w-[420px]"
:mask-closable="false"
@before-leave="onAfterLeave"
>
<NForm id="select-user-form" ref="formRef" :model="reportStore.reportInfo" :rules="rules">
<NFormItem label="Выберите ответственного" path="userId">
<NSelect :options="users" v-model:value="reportStore.reportInfo.userId" />
</NFormItem>
</NForm>
<template #action>
<NButton form-id="select-user-form"
type="primary"
block
@click="onSubmit">
Перейти к заполнению сводной
</NButton>
</template>
</NModal>
</template>
<style scoped>
</style>

View File

@@ -8,10 +8,18 @@ import {useAuthStore} from "../../Stores/auth.js";
const reportStore = useReportStore()
const authStore = useAuthStore()
onMounted(async () => {
await reportStore.getReportInfo()
onMounted(() => {
const queryString = window.location.search
const params = new URLSearchParams(queryString)
const userId = params.get('userId')
reportStore.reportInfo.userId = userId
reportStore.getReportInfo()
})
// reportStore.getReportInfo()
const mode = computed(() => {
if (authStore.isHeadOfDepartment)
return 'readonly'

View File

@@ -23,7 +23,9 @@ export const useReportStore = defineStore('reportStore', () => {
const dataOnReport = ref(null)
const reportInfo = ref(null)
const reportInfo = ref({
userId: null
})
const isLoadReportInfo = ref(false)
const patientColumns = [
@@ -60,6 +62,9 @@ export const useReportStore = defineStore('reportStore', () => {
const reportForm = ref({})
// Пользователи которые находятся в том же отделении что и авторизованный пользователь
const departmentUsers = ref([])
// Нежелательные события
const unwantedEvents = ref([])
@@ -79,6 +84,7 @@ export const useReportStore = defineStore('reportStore', () => {
observationPatients: patientsData.value['observation'],
unwantedEvents: unwantedEvents.value,
dates: timestampCurrentRange.value,
userId: reportInfo.value.userId,
...assignForm
}
@@ -102,13 +108,25 @@ export const useReportStore = defineStore('reportStore', () => {
const getReportInfo = async () => {
isLoadReportInfo.value = true
await axios.get('/api/report')
const queryParams = {
userId: reportInfo.value.userId
}
await axios.get(`/api/report`, {
params: queryParams
})
.then((res) => {
reportInfo.value = res.data
reportInfo.value = {
...reportInfo.value,
...res.data
}
reportForm.value.metrika_item_3 = reportInfo.value.department?.recipientCount
reportForm.value.metrika_item_7 = reportInfo.value.department?.extractCount
reportForm.value.metrika_item_8 = reportInfo.value.department?.currentCount
reportForm.value.metrika_item_8 = reportInfo.value.department?.currentCounts
reportForm.value.metrika_item_9 = reportInfo.value.department?.deadCount
reportForm.value.metrika_item_10 = reportInfo.value.department?.surgicalCount[1]
reportForm.value.metrika_item_11 = reportInfo.value.department?.surgicalCount[0]
unwantedEvents.value = reportInfo.value.report.unwantedEvents
@@ -125,9 +143,19 @@ export const useReportStore = defineStore('reportStore', () => {
const getDataOnReportDate = async (dateRange) => {
isLoadReportInfo.value = true
timestampCurrentRange.value = dateRange
await axios.get(`/api/report?startAt=${timestampCurrentRange.value[0]}&endAt=${timestampCurrentRange.value[1]}`)
const queryParams = {
userId: reportInfo.value.userId,
startAt: timestampCurrentRange.value[0],
endAt: timestampCurrentRange.value[1],
}
await axios.get(`/api/report`, {
params: queryParams
})
.then((res) => {
reportInfo.value = res.data
reportInfo.value = {
...reportInfo.value,
...res.data
}
reportForm.value.metrika_item_3 = reportInfo.value.department?.recipientCount
reportForm.value.metrika_item_7 = reportInfo.value.department?.extractCount
@@ -155,6 +183,7 @@ export const useReportStore = defineStore('reportStore', () => {
patientsData,
reportInfo,
reportForm,
departmentUsers,
unwantedEvents,
getColumnsByKey,