Роли, переделывание отчета, изменение на главной странице
This commit is contained in:
@@ -34,7 +34,7 @@ const themeOverrides = {
|
||||
<!-- <SideMenu />-->
|
||||
<!-- </NLayoutSider>-->
|
||||
|
||||
<NLayout content-class="pl-4">
|
||||
<NLayout>
|
||||
<div>
|
||||
<slot name="header" />
|
||||
</div>
|
||||
|
||||
@@ -3,24 +3,21 @@ import { NFlex, NSpace, NDivider, NButton } from 'naive-ui'
|
||||
import ReportSelectDate from "../../Components/ReportSelectDate.vue";
|
||||
import AppUserButton from "./AppUserButton.vue";
|
||||
import {Link} from "@inertiajs/vue3";
|
||||
import AppHeaderRole from "./AppHeaderRole.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NFlex justify="space-between" align="center" class="px-4 w-full h-full">
|
||||
<NSpace align="center">
|
||||
<NFlex align="center">
|
||||
<NButton :tag="Link" text href="/">
|
||||
Метрика
|
||||
</NButton>
|
||||
<NDivider vertical />
|
||||
<ReportSelectDate />
|
||||
</NSpace>
|
||||
<NSpace align="center">
|
||||
<NButton :tag="Link" text href="/">
|
||||
Мои отчеты??
|
||||
</NButton>
|
||||
<NDivider vertical />
|
||||
<!-- <NDivider vertical />-->
|
||||
<!-- <ReportSelectDate />-->
|
||||
</NFlex>
|
||||
<NFlex align="center">
|
||||
<AppUserButton />
|
||||
</NSpace>
|
||||
</NFlex>
|
||||
</NFlex>
|
||||
</template>
|
||||
|
||||
|
||||
35
resources/js/Layouts/Components/AppHeaderRole.vue
Normal file
35
resources/js/Layouts/Components/AppHeaderRole.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script setup>
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import {NSelect} from 'naive-ui'
|
||||
|
||||
const roles = ref([])
|
||||
|
||||
const onFetchUserRoles = async () => {
|
||||
await axios.get('/api/app/user/roles').then(res => {
|
||||
roles.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
const roleOptions = computed(() => {
|
||||
if (roles.value.length > 0) return roles.value.map(itm => ({
|
||||
label: itm.name,
|
||||
value: itm.role_id
|
||||
}))
|
||||
})
|
||||
|
||||
const value = ref(null)
|
||||
|
||||
onMounted(async () => {
|
||||
await onFetchUserRoles()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<NSelect v-model:value="value" :options="roleOptions" class="w-50!" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import {useAuthStore} from "../../Stores/auth.js";
|
||||
import {NDropdown, NButton} from 'naive-ui'
|
||||
import {NDropdown, NButton, NText} from 'naive-ui'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const userOptions = [
|
||||
@@ -20,11 +20,14 @@ const themeOverride = {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDropdown :options="userOptions" placement="bottom-end">
|
||||
<NButton :theme-overrides="themeOverride">
|
||||
{{ authStore.user?.name }}
|
||||
</NButton>
|
||||
</NDropdown>
|
||||
<NText>
|
||||
{{ authStore.user?.name }}
|
||||
</NText>
|
||||
<!-- <NDropdown :options="userOptions" placement="bottom-end">-->
|
||||
<!-- <NButton :theme-overrides="themeOverride">-->
|
||||
<!-- {{ authStore.user?.name }}-->
|
||||
<!-- </NButton>-->
|
||||
<!-- </NDropdown>-->
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -7,7 +7,7 @@ import {computed} from "vue";
|
||||
import {format} from "date-fns";
|
||||
import {ru} from "date-fns/locale";
|
||||
import {useNow} from "@vueuse/core";
|
||||
import {TbArticle, TbChartTreemap} from "vue-icons-plus/tb";
|
||||
import {TbArticle, TbChartTreemap, TbDoorExit} from "vue-icons-plus/tb";
|
||||
import {useReportStore} from "../Stores/report.js";
|
||||
|
||||
const authStore = useAuthStore()
|
||||
@@ -37,7 +37,7 @@ const currentDate = computed(() => {
|
||||
|
||||
<StartButton title="Заполнить сводную"
|
||||
description="Заполняется регулярно"
|
||||
href="/dashboard"
|
||||
href="/report"
|
||||
:icon="TbArticle"
|
||||
/>
|
||||
<StartButton title="Статистика моего отделения"
|
||||
@@ -45,9 +45,10 @@ const currentDate = computed(() => {
|
||||
:href="`/statistic?sent_at=${reportStore.timestampCurrentRange}&groupId=1`"
|
||||
:icon="TbChartTreemap"
|
||||
/>
|
||||
<StartButton title="Заполнить сводную"
|
||||
description="Заполняется регулярно"
|
||||
<StartButton title="Выйти из системы"
|
||||
description="Завершение работы с текущей учетной записью"
|
||||
href="/dashboard"
|
||||
:icon="TbDoorExit"
|
||||
/>
|
||||
</NFlex>
|
||||
</div>
|
||||
|
||||
116
resources/js/Pages/Path/Patient.vue
Normal file
116
resources/js/Pages/Path/Patient.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import {useVueFlow, VueFlow} from '@vue-flow/core'
|
||||
import AppLayout from "../../Layouts/AppLayout.vue";
|
||||
|
||||
const props = defineProps({
|
||||
path: {
|
||||
type: Object,
|
||||
default: {}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const elements = ref([
|
||||
// Ноды (блоки)
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
label: 'Поступление',
|
||||
position: { x: 100, y: 50 },
|
||||
data: {
|
||||
patient: 'Иванов И.И.',
|
||||
time: '09:00',
|
||||
status: 'arrival'
|
||||
},
|
||||
class: 'node-arrival'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
label: 'Первичный осмотр',
|
||||
position: { x: 300, y: 50 },
|
||||
data: {
|
||||
doctor: 'Петров П.П.',
|
||||
diagnosis: 'Предварительный',
|
||||
duration: '30 мин'
|
||||
},
|
||||
class: 'node-examination'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
label: 'Анализы',
|
||||
position: { x: 500, y: 50 },
|
||||
data: {
|
||||
tests: ['Кровь', 'Моча', 'УЗИ'],
|
||||
lab: 'Лаборатория №1'
|
||||
},
|
||||
class: 'node-tests'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
label: 'Лечение',
|
||||
position: { x: 300, y: 200 },
|
||||
data: {
|
||||
procedures: ['Капельница', 'Уколы'],
|
||||
ward: 'Палата 304'
|
||||
},
|
||||
class: 'node-treatment'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
type: 'output',
|
||||
label: 'Выписка',
|
||||
position: { x: 100, y: 350 },
|
||||
data: {
|
||||
outcome: 'Выздоровление',
|
||||
date: '2024-01-15'
|
||||
},
|
||||
class: 'node-discharge'
|
||||
},
|
||||
])
|
||||
|
||||
const edges = ref([
|
||||
{ id: 'e1-2', source: '1', target: '2', label: 'Направлен' },
|
||||
{ id: 'e2-3', source: '2', target: '3', label: 'На анализы' },
|
||||
{ id: 'e3-4', source: '3', target: '4', label: 'Результаты' },
|
||||
{ id: 'e4-5', source: '4', target: '5', label: 'Завершено' }
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout>
|
||||
<div class="h-[calc(100vh-48px)]">
|
||||
<VueFlow v-model:nodes="elements" v-model:edges="edges" :fit-view-on-init="true">
|
||||
<template #node-custom="{ data, label }">
|
||||
<div class="patient-node">
|
||||
<div class="node-icon">
|
||||
<span v-if="data.status === 'arrival'">🏥</span>
|
||||
<span v-else-if="data.doctor">👨⚕️</span>
|
||||
<span v-else-if="data.tests">🧪</span>
|
||||
<span v-else-if="data.procedures">💉</span>
|
||||
<span v-else>✅</span>
|
||||
</div>
|
||||
<div class="node-info">
|
||||
<strong>{{ label }}</strong>
|
||||
<div v-if="data.patient" class="patient-name">
|
||||
{{ data.patient }}
|
||||
</div>
|
||||
<div v-if="data.time" class="node-time">
|
||||
⏰ {{ data.time }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</VueFlow>
|
||||
</div>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Цвета для разных типов нод */
|
||||
.node-arrival { border-color: #4CAF50; }
|
||||
.node-examination { border-color: #2196F3; }
|
||||
.node-tests { border-color: #FF9800; }
|
||||
.node-treatment { border-color: #9C27B0; }
|
||||
.node-discharge { border-color: #607D8B; }
|
||||
</style>
|
||||
42
resources/js/Pages/Report/Components/ReportForm.vue
Normal file
42
resources/js/Pages/Report/Components/ReportForm.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<script setup>
|
||||
import { NFlex, NButton } from 'naive-ui'
|
||||
import ReportHeader from "./ReportHeader.vue";
|
||||
import ReportFormInput from "./ReportFormInput.vue";
|
||||
import ReportSection from "./ReportSection.vue";
|
||||
import {useReportStore} from "../../../Stores/report.js";
|
||||
import {useAuthStore} from "../../../Stores/auth.js";
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'fillable' // 'fillable', 'readonly'
|
||||
}
|
||||
})
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const reportStore = useReportStore()
|
||||
|
||||
const onSubmit = () => {
|
||||
reportStore.sendReportForm({
|
||||
departmentId: authStore.userDepartment.department_id
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NFlex vertical class="max-w-6xl mx-auto mt-6 mb-4 w-full">
|
||||
<ReportHeader :mode="mode" />
|
||||
|
||||
<ReportFormInput />
|
||||
|
||||
<ReportSection label="Планово" />
|
||||
|
||||
<NButton secondary size="large" @click="onSubmit">
|
||||
Сохранить отчет
|
||||
</NButton>
|
||||
</NFlex>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
25
resources/js/Pages/Report/Components/ReportFormInput.vue
Normal file
25
resources/js/Pages/Report/Components/ReportFormInput.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<script setup>
|
||||
import {NCard, NFlex, NFormItem, NForm, NInputNumber} from "naive-ui";
|
||||
import {useReportStore} from "../../../Stores/report.js";
|
||||
|
||||
const reportStore = useReportStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard>
|
||||
<NForm>
|
||||
<NFlex>
|
||||
<template v-for="metrikaItem in reportStore.reportInfo?.metrikaItems">
|
||||
<NFormItem :label="metrikaItem.name" :show-feedback="false">
|
||||
<NInputNumber v-model:value="reportStore.reportForm[`metrika_item_${metrikaItem.metrika_item_id}`]"
|
||||
:default-value="metrikaItem.default_value" />
|
||||
</NFormItem>
|
||||
</template>
|
||||
</NFlex>
|
||||
</NForm>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
88
resources/js/Pages/Report/Components/ReportHeader.vue
Normal file
88
resources/js/Pages/Report/Components/ReportHeader.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<script setup>
|
||||
import {NStatistic, NRow, NCol, 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";
|
||||
import {ru} from "date-fns/locale";
|
||||
import {useAuthStore} from "../../../Stores/auth.js";
|
||||
import {storeToRefs} from "pinia";
|
||||
import {RiAddCircleLine} from 'vue-icons-plus/ri'
|
||||
import {useReportStore} from "../../../Stores/report.js";
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'fillable' // 'fillable', 'readonly'
|
||||
},
|
||||
isShowDepartmentSelect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
})
|
||||
|
||||
const departments = [
|
||||
{
|
||||
label: 'Все отделения',
|
||||
value: 0
|
||||
}
|
||||
]
|
||||
const authStore = useAuthStore()
|
||||
const reportStore = useReportStore()
|
||||
const {user, availableDepartments} = storeToRefs(authStore)
|
||||
const {reportInfo} = storeToRefs(reportStore)
|
||||
|
||||
const isFillableMode = computed(() => props.mode.toLowerCase() === 'fillable')
|
||||
const isReadonlyMode = computed(() => props.mode.toLowerCase() === 'readonly')
|
||||
|
||||
const selectDepartment = ref(0)
|
||||
|
||||
const currentDate = computed(() => {
|
||||
const formatted = format(useNow().value, 'PPPPpp', {
|
||||
locale: ru
|
||||
})
|
||||
|
||||
return formatted.charAt(0).toUpperCase() + formatted.slice(1)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard>
|
||||
<NFlex vertical>
|
||||
<NFlex align="center" justify="space-between">
|
||||
<NH2 v-if="isFillableMode" class="mb-0!">
|
||||
{{ currentDate }}
|
||||
</NH2>
|
||||
<NDatePicker v-if="isReadonlyMode" />
|
||||
|
||||
<NFlex align="center" :wrap="false">
|
||||
<NTag v-if="isFillableMode" type="info" :bordered="false">
|
||||
{{ authStore.userDepartment.name_full }}
|
||||
</NTag>
|
||||
<NSelect v-if="isReadonlyMode" v-model:value="selectDepartment" :options="departments" />
|
||||
</NFlex>
|
||||
</NFlex>
|
||||
|
||||
<NFlex justify="space-between" align="center" :wrap="false">
|
||||
<NRow class="grow">
|
||||
<NCol :span="4">
|
||||
<NStatistic label="Коек" :value="reportStore.reportInfo?.department.beds" />
|
||||
</NCol>
|
||||
<NCol :span="5">
|
||||
<NStatistic label="Загруженность" :value="`${reportStore.reportInfo?.department.percentLoadedBeds}%`" />
|
||||
</NCol>
|
||||
</NRow>
|
||||
|
||||
<NButton type="primary" secondary>
|
||||
<template #icon>
|
||||
<RiAddCircleLine />
|
||||
</template>
|
||||
Нежелательное событие
|
||||
</NButton>
|
||||
</NFlex>
|
||||
</NFlex>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
90
resources/js/Pages/Report/Components/ReportSection.vue
Normal file
90
resources/js/Pages/Report/Components/ReportSection.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<script setup>
|
||||
import {NCard, NFlex, NAlert, NCollapse, NCollapseItem} from 'naive-ui'
|
||||
import ReportSectionItem from "./ReportSectionItem.vue";
|
||||
import {computed} from "vue";
|
||||
import {useReportStore} from "../../../Stores/report.js";
|
||||
import {storeToRefs} from "pinia";
|
||||
import ReportSectionHeader from "./ReportSectionHeader.vue";
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'fillable' // 'fillable', 'readonly'
|
||||
}
|
||||
})
|
||||
|
||||
const reportStore = useReportStore()
|
||||
const {patientsData} = storeToRefs(reportStore)
|
||||
|
||||
const handleItemDragged = (event) => {
|
||||
console.log('Начато перетаскивание:', event)
|
||||
}
|
||||
|
||||
// Обработка события drop
|
||||
const handleItemDropped = (event) => {
|
||||
const { item, fromStatus, toStatus } = event
|
||||
|
||||
console.log(event)
|
||||
|
||||
// Добавляем в целевую таблицу
|
||||
if (toStatus && patientsData.value[toStatus]) {
|
||||
// Проверяем, нет ли уже такого элемента
|
||||
if (!patientsData.value[toStatus].some(i => i.id === item.id)) {
|
||||
patientsData.value[toStatus].push(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isFillableMode = computed(() => props.mode.toLowerCase() === 'fillable')
|
||||
const isReadonlyMode = computed(() => props.mode.toLowerCase() === 'readonly')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NCard>
|
||||
<NCollapse>
|
||||
<NCollapseItem name="1">
|
||||
<template #header>
|
||||
<ReportSectionHeader title="Планово" status="plan" />
|
||||
</template>
|
||||
<ReportSectionItem status="plan"
|
||||
@item-dragged="handleItemDragged"
|
||||
/>
|
||||
</NCollapseItem>
|
||||
<NCollapseItem name="2">
|
||||
<template #header>
|
||||
<ReportSectionHeader title="Экстренно" status="emergency" />
|
||||
</template>
|
||||
<ReportSectionItem status="emergency"
|
||||
@item-dragged="handleItemDragged"
|
||||
/>
|
||||
</NCollapseItem>
|
||||
<NCollapseItem name="3">
|
||||
<template #header>
|
||||
<ReportSectionHeader title="Наблюдение" status="observation" />
|
||||
</template>
|
||||
<NFlex :size="12">
|
||||
<ReportSectionItem status="observation"
|
||||
@item-dragged="handleItemDragged"
|
||||
@item-dropped="handleItemDropped"
|
||||
/>
|
||||
<NAlert v-if="isFillableMode" type="info" class="w-full">
|
||||
Перетаскивайте строки из верхних таблиц, что бы добавить в наблюдение
|
||||
</NAlert>
|
||||
</NFlex>
|
||||
</NCollapseItem>
|
||||
<NCollapseItem name="4">
|
||||
<template #header>
|
||||
<ReportSectionHeader title="Умершие" status="deceased" />
|
||||
</template>
|
||||
<ReportSectionItem status="deceased"
|
||||
@item-dragged="handleItemDragged"
|
||||
@item-dropped="handleItemDropped"
|
||||
/>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
55
resources/js/Pages/Report/Components/ReportSectionHeader.vue
Normal file
55
resources/js/Pages/Report/Components/ReportSectionHeader.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<script setup>
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import {NSkeleton, NText} from 'naive-ui'
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
const isLoading = ref(true)
|
||||
const countPatient = ref(null)
|
||||
const fetchPatientCount = async () => {
|
||||
if (props.status === 'plan' || props.status === 'emergency') {
|
||||
isLoading.value = true
|
||||
await axios.post('/api/mis/patients/count', {
|
||||
status: props.status
|
||||
}).then((res) => {
|
||||
countPatient.value = res.data
|
||||
}).finally(() => {
|
||||
isLoading.value = false
|
||||
})
|
||||
} else {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const computedHeader = computed(() => {
|
||||
if (countPatient.value !== null) {
|
||||
return `${props.title} (${countPatient.value})`
|
||||
} else {
|
||||
return props.title
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchPatientCount()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSkeleton text style="width: 128px; height: 22px" round v-if="isLoading" />
|
||||
<NText v-else>
|
||||
{{ computedHeader }}
|
||||
</NText>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
166
resources/js/Pages/Report/Components/ReportSectionItem.vue
Normal file
166
resources/js/Pages/Report/Components/ReportSectionItem.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<script setup>
|
||||
import {NDataTable} 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";
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'fillable' // 'fillable', 'readonly'
|
||||
},
|
||||
keys: {
|
||||
type: Array,
|
||||
default: ['num', 'fullname', 'age', 'birth_date', 'ds']
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
default: null // 'plan'
|
||||
}
|
||||
})
|
||||
|
||||
const isFillableMode = computed(() => props.mode.toLowerCase() === 'fillable')
|
||||
const isReadonlyMode = computed(() => props.mode.toLowerCase() === 'readonly')
|
||||
|
||||
const emit = defineEmits(['item-dragged', 'item-dropped'])
|
||||
|
||||
const reportStore = useReportStore()
|
||||
const {patientsData} = storeToRefs(reportStore)
|
||||
// Получаем базовые колонки
|
||||
const baseColumns = reportStore.getColumnsByKey(props.keys)
|
||||
const data = ref([])
|
||||
const isLoading = ref(true)
|
||||
|
||||
// Добавляем drag колонку если режим fillable
|
||||
const columns = computed(() => {
|
||||
if (!isFillableMode.value) return baseColumns
|
||||
|
||||
const dragColumn = {
|
||||
title: '',
|
||||
key: 'drag',
|
||||
width: 40,
|
||||
render: (row) => h('div', {
|
||||
style: {
|
||||
cursor: 'grab',
|
||||
color: '#666',
|
||||
textAlign: 'center',
|
||||
userSelect: 'none'
|
||||
}
|
||||
}, '⋮⋮')
|
||||
}
|
||||
|
||||
return [dragColumn, ...baseColumns]
|
||||
})
|
||||
|
||||
const handleDragStart = (e, row) => {
|
||||
// Устанавливаем данные о перетаскиваемом элементе
|
||||
e.dataTransfer.setData('application/json', JSON.stringify({
|
||||
row: row,
|
||||
fromStatus: props.status
|
||||
}))
|
||||
e.dataTransfer.effectAllowed = 'copy'
|
||||
|
||||
const rowElement = e.target
|
||||
|
||||
// Эмитим событие для родителя
|
||||
emit('item-dragged', { row, fromStatus: props.status })
|
||||
|
||||
// Добавляем класс для визуальной обратной связи
|
||||
e.target.classList.add('dragging')
|
||||
}
|
||||
|
||||
const handleDragEnd = (e) => {
|
||||
if (e.target) {
|
||||
e.target.classList.remove('dragging')
|
||||
}
|
||||
}
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
const handleDrop = (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
try {
|
||||
const dragData = JSON.parse(e.dataTransfer.getData('application/json'))
|
||||
|
||||
// Эмитим событие для родителя
|
||||
emit('item-dropped', {
|
||||
item: dragData.row,
|
||||
fromStatus: dragData.fromStatus,
|
||||
toStatus: props.status
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Drop error:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchPatients = async () => {
|
||||
if (props.status === 'plan' || props.status === 'emergency') {
|
||||
isLoading.value = true
|
||||
await axios.post('/api/mis/patients', {
|
||||
status: props.status
|
||||
}).then((res) => {
|
||||
patientsData.value[props.status] = res.data
|
||||
}).finally(() => {
|
||||
isLoading.value = false
|
||||
})
|
||||
} else {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function rowProps(row) {
|
||||
return {
|
||||
draggable: true,
|
||||
style: 'cursor: grab;',
|
||||
onDragstart: (e) => {
|
||||
handleDragStart(e, row)
|
||||
},
|
||||
onDragend: (e) => {
|
||||
handleDragEnd(e)
|
||||
},
|
||||
onDragover: (e) => {
|
||||
handleDragOver(e)
|
||||
},
|
||||
onDrop: (e) => {
|
||||
handleDrop(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchPatients()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDataTable :columns="columns"
|
||||
:data="patientsData[status]"
|
||||
size="small"
|
||||
@drop="handleDrop"
|
||||
@dragover="handleDragOver"
|
||||
:loading="isLoading"
|
||||
virtual-scroll
|
||||
max-height="200"
|
||||
min-height="200"
|
||||
:row-props="rowProps"
|
||||
class="text-sm!">
|
||||
</NDataTable>
|
||||
<!-- <NDataTable :columns="columns"-->
|
||||
<!-- :data="data"-->
|
||||
<!-- size="small"-->
|
||||
<!-- max-height="200"-->
|
||||
<!-- class="text-sm!">-->
|
||||
<!-- </NDataTable>-->
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.n-data-table-th),
|
||||
:deep(.n-data-table-td) {
|
||||
white-space: nowrap !important;
|
||||
font-size: var(--n-font-size);
|
||||
}
|
||||
</style>
|
||||
@@ -1,19 +1,18 @@
|
||||
<script setup>
|
||||
import AppLayout from "../../Layouts/AppLayout.vue";
|
||||
import {NList, NListItem, NFlex, NInput, NButton} from 'naive-ui'
|
||||
import {useForm} from "@inertiajs/vue3";
|
||||
import {computed} from "vue";
|
||||
import MetrikaForm from "../Metriks/Components/MetrikaForm.vue";
|
||||
import ReportForm from "./Components/ReportForm.vue";
|
||||
import {useReportStore} from "../../Stores/report.js";
|
||||
import {onMounted} from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
metriks: {
|
||||
type: Array
|
||||
}
|
||||
const reportStore = useReportStore()
|
||||
|
||||
onMounted(async () => {
|
||||
await reportStore.getReportInfo()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout>
|
||||
<MetrikaForm :group-id="1" />
|
||||
<ReportForm />
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
@@ -1,58 +1,135 @@
|
||||
<script setup>
|
||||
import {NDataTable} from 'naive-ui'
|
||||
import AppLayout from "../../Layouts/AppLayout.vue";
|
||||
import {NList, NListItem, NFlex, NText, NH1, NTag} from 'naive-ui'
|
||||
import {ref} from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
is_view_only: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
period: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
group: {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
metrics: {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
form: {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
default: []
|
||||
}
|
||||
})
|
||||
|
||||
const columns = ref([
|
||||
{
|
||||
title: 'Отделение',
|
||||
key: 'department',
|
||||
width: 240,
|
||||
titleAlign: 'center'
|
||||
},
|
||||
{
|
||||
title: 'Кол-во коек',
|
||||
key: 'beds',
|
||||
width: 60,
|
||||
titleAlign: 'center',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: 'Состояло',
|
||||
key: 'beds',
|
||||
width: 84,
|
||||
titleAlign: 'center',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: 'Поступило',
|
||||
key: 'received',
|
||||
titleAlign: 'center',
|
||||
children: [
|
||||
{
|
||||
title: 'Всего',
|
||||
key: 'all',
|
||||
width: 60,
|
||||
titleAlign: 'center',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: 'План',
|
||||
key: 'plan',
|
||||
width: 60,
|
||||
titleAlign: 'center',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: 'Экстр',
|
||||
key: 'emergency',
|
||||
width: 60,
|
||||
titleAlign: 'center',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: 'Перевод',
|
||||
key: '',
|
||||
width: 84,
|
||||
titleAlign: 'center',
|
||||
align: 'center'
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Выбыло',
|
||||
key: 'leave',
|
||||
width: 84,
|
||||
titleAlign: 'center',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: 'Состоит',
|
||||
key: 'consist',
|
||||
width: 84,
|
||||
titleAlign: 'center',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '% загруженности',
|
||||
key: 'percentLoadedBeds',
|
||||
width: 84,
|
||||
titleAlign: 'center',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: 'Операции',
|
||||
key: '',
|
||||
titleAlign: 'center',
|
||||
children: [
|
||||
{
|
||||
title: 'Э',
|
||||
key: '',
|
||||
width: 60,
|
||||
titleAlign: 'center',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: 'П',
|
||||
key: '',
|
||||
width: 60,
|
||||
titleAlign: 'center',
|
||||
align: 'center'
|
||||
},
|
||||
]
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout>
|
||||
<div class="max-w-6xl mx-auto mt-4 mb-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<n-h1 class="!mb-2">{{ group.name }}</n-h1>
|
||||
<n-text depth="3" v-if="group.description">
|
||||
{{ group.description }}
|
||||
</n-text>
|
||||
</div>
|
||||
</div>
|
||||
<NList>
|
||||
<NListItem v-for="metric in metrics.values">
|
||||
<NFlex justify="space-between" align="center" class="w-full px-4">
|
||||
<div>
|
||||
{{metric.item_name}}
|
||||
</div>
|
||||
<div>
|
||||
{{metric.sum}}
|
||||
</div>
|
||||
</NFlex>
|
||||
</NListItem>
|
||||
</NList>
|
||||
</div>
|
||||
</AppLayout>
|
||||
<AppLayout>
|
||||
<NDataTable :columns="columns"
|
||||
:data="data"
|
||||
size="small"
|
||||
:single-line="false"
|
||||
striped
|
||||
min-height="calc(100vh - 48px - 70px)"
|
||||
max-height="calc(100vh - 48px - 70px)"
|
||||
>
|
||||
|
||||
</NDataTable>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
:deep(.n-data-table-th),
|
||||
:deep(.n-data-table-td) {
|
||||
//white-space: nowrap !important;
|
||||
font-size: var(--n-font-size);
|
||||
}
|
||||
</style>
|
||||
|
||||
58
resources/js/Pages/Statistic/IndexOld.vue
Normal file
58
resources/js/Pages/Statistic/IndexOld.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<script setup>
|
||||
import AppLayout from "../../Layouts/AppLayout.vue";
|
||||
import {NList, NListItem, NFlex, NText, NH1, NTag} from 'naive-ui'
|
||||
|
||||
const props = defineProps({
|
||||
is_view_only: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
period: {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
group: {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
metrics: {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
form: {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout>
|
||||
<div class="max-w-6xl mx-auto mt-4 mb-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<n-h1 class="!mb-2">{{ group.name }}</n-h1>
|
||||
<n-text depth="3" v-if="group.description">
|
||||
{{ group.description }}
|
||||
</n-text>
|
||||
</div>
|
||||
</div>
|
||||
<NList>
|
||||
<NListItem v-for="metric in metrics.values">
|
||||
<NFlex justify="space-between" align="center" class="w-full px-4">
|
||||
<div>
|
||||
{{metric.item_name}}
|
||||
</div>
|
||||
<div>
|
||||
{{metric.sum}}
|
||||
</div>
|
||||
</NFlex>
|
||||
</NListItem>
|
||||
</NList>
|
||||
</div>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -7,7 +7,7 @@ export const useAuthStore = defineStore('authStore', () => {
|
||||
const user = usePage().props.user
|
||||
const token = user?.token
|
||||
const permissions = user?.permissions
|
||||
const availableDepartments = ref([])
|
||||
const availableDepartments = ref(user?.available_departments)
|
||||
|
||||
// Инициализация axios с токеном
|
||||
if (token?.value) {
|
||||
@@ -21,7 +21,7 @@ export const useAuthStore = defineStore('authStore', () => {
|
||||
const isNurse = computed(() => user.role === 'nurse')
|
||||
const isHeadOfDepartment = computed(() => user.role === 'head_of_department')
|
||||
const isStatistician = computed(() => user.role === 'statistician')
|
||||
const userDepartment = computed(() => user.department || '')
|
||||
const userDepartment = computed(() => user.current_department || '')
|
||||
|
||||
const clearAuthData = () => {
|
||||
user.value = null
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {defineStore} from "pinia";
|
||||
import {useTimestamp} from "@vueuse/core";
|
||||
import {computed, ref} from "vue";
|
||||
import {router} from "@inertiajs/vue3";
|
||||
|
||||
export const useReportStore = defineStore('reportStore', () => {
|
||||
const timestampNow = useTimestamp()
|
||||
@@ -22,6 +23,82 @@ export const useReportStore = defineStore('reportStore', () => {
|
||||
|
||||
const dataOnReport = ref(null)
|
||||
|
||||
const reportInfo = ref(null)
|
||||
|
||||
const patientColumns = [
|
||||
{
|
||||
title: '№',
|
||||
width: '80',
|
||||
key: 'num'
|
||||
},
|
||||
{
|
||||
title: 'ФИО',
|
||||
width: '320',
|
||||
key: 'fullname'
|
||||
},
|
||||
{
|
||||
title: 'Возраст',
|
||||
key: 'age'
|
||||
},
|
||||
{
|
||||
title: 'Дата рождения',
|
||||
key: 'birth_date'
|
||||
},
|
||||
{
|
||||
title: 'Диагноз',
|
||||
key: 'ds'
|
||||
}
|
||||
]
|
||||
|
||||
const patientsData = ref({
|
||||
plan: [],
|
||||
emergency: [],
|
||||
observation: [],
|
||||
deceased: []
|
||||
})
|
||||
|
||||
const reportForm = ref({})
|
||||
|
||||
const getColumnsByKey = (keys) => {
|
||||
const result = []
|
||||
for (const key of keys) {
|
||||
const column = patientColumns.find(item => item.key === key)
|
||||
result.push(column)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const sendReportForm = (assignForm) => {
|
||||
const form = {
|
||||
metrics: reportForm.value,
|
||||
observationPatients: patientsData.value['observation'],
|
||||
...assignForm
|
||||
}
|
||||
|
||||
axios.post('/api/report', form)
|
||||
.then(r => {
|
||||
window.$message.success('Отчет сохранен')
|
||||
resetReportForm()
|
||||
router.visit('/')
|
||||
})
|
||||
}
|
||||
|
||||
const resetReportForm = () => {
|
||||
reportForm.value = {}
|
||||
patientsData.value.observation = []
|
||||
}
|
||||
|
||||
const $reset = () => {
|
||||
|
||||
}
|
||||
|
||||
const getReportInfo = async () => {
|
||||
await axios.get('/api/report').then((res) => {
|
||||
reportInfo.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
const getDataOnReportDate = async () => {
|
||||
await axios.get(`/api/metric-forms/1/report-by-date?sent_at=${timestampCurrentRange.value}`)
|
||||
.then(res => {
|
||||
@@ -38,7 +115,15 @@ export const useReportStore = defineStore('reportStore', () => {
|
||||
timestampCurrent,
|
||||
timestampCurrentRange,
|
||||
dataOnReport,
|
||||
patientColumns,
|
||||
patientsData,
|
||||
reportInfo,
|
||||
reportForm,
|
||||
|
||||
getDataOnReportDate
|
||||
getColumnsByKey,
|
||||
getDataOnReportDate,
|
||||
getReportInfo,
|
||||
sendReportForm,
|
||||
resetReportForm,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import './bootstrap';
|
||||
import '../css/app.css';
|
||||
import { createApp, h } from 'vue'
|
||||
import { createInertiaApp } from '@inertiajs/vue3'
|
||||
import {createPinia} from "pinia";
|
||||
import {setupNaiveDiscreteApi} from "./Plugins/NaiveUI.js";
|
||||
import './bootstrap';
|
||||
import '../css/app.css';
|
||||
|
||||
|
||||
createInertiaApp({
|
||||
id: 'onboard',
|
||||
|
||||
Reference in New Issue
Block a user