Роли, переделывание отчета, изменение на главной странице
This commit is contained in:
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>
|
||||
Reference in New Issue
Block a user