413 lines
12 KiB
Vue
413 lines
12 KiB
Vue
<script setup>
|
|
import {computed, h, reactive, ref, watch} from "vue";
|
|
import {
|
|
NButton,
|
|
NDataTable,
|
|
NDatePicker,
|
|
NEllipsis,
|
|
NForm,
|
|
NFormItem,
|
|
NIcon,
|
|
NModal,
|
|
NPopconfirm,
|
|
NSelect,
|
|
NSpin,
|
|
NText,
|
|
} from "naive-ui";
|
|
import {TbPencil, TbTrash} from "vue-icons-plus/tb";
|
|
import {useDebounceFn} from "@vueuse/core";
|
|
import {useReportStore} from "../../../Stores/report.js";
|
|
|
|
const props = defineProps({
|
|
patient: {
|
|
type: Object,
|
|
default: null,
|
|
},
|
|
sourceStatus: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
})
|
|
|
|
const show = defineModel('show', {type: Boolean, default: false})
|
|
const reportStore = useReportStore()
|
|
const formRef = ref(null)
|
|
const loading = ref(false)
|
|
const submitting = ref(false)
|
|
const operations = ref([])
|
|
const serviceLoading = ref(false)
|
|
const serviceOptions = ref([])
|
|
const editingOperationId = ref(null)
|
|
|
|
const form = reactive({
|
|
service_id: null,
|
|
service_code: '',
|
|
service_name: '',
|
|
urgency: 'plan',
|
|
period: null,
|
|
})
|
|
|
|
const rules = {
|
|
service_id: {
|
|
required: true,
|
|
message: 'Укажите услугу',
|
|
trigger: ['change'],
|
|
validator: (_rule, value) => {
|
|
if (value === null || value === undefined || value === '') return false
|
|
const id = Number(value)
|
|
return Number.isInteger(id) && id > 0
|
|
},
|
|
},
|
|
urgency: {
|
|
required: true,
|
|
message: 'Укажите срочность операции',
|
|
trigger: ['change'],
|
|
},
|
|
period: {
|
|
required: true,
|
|
type: 'array',
|
|
message: 'Укажите дату и время начала/окончания',
|
|
trigger: ['change'],
|
|
validator: (_rule, value) => {
|
|
if (!Array.isArray(value) || value.length !== 2) return false
|
|
if (!value[0] || !value[1]) return false
|
|
return value[1] >= value[0]
|
|
},
|
|
},
|
|
}
|
|
|
|
const resetForm = () => {
|
|
editingOperationId.value = null
|
|
form.service_id = null
|
|
form.service_code = ''
|
|
form.service_name = ''
|
|
form.urgency = 'plan'
|
|
form.period = null
|
|
serviceOptions.value = []
|
|
}
|
|
|
|
const urgencyOptions = [
|
|
{ label: 'Плановая', value: 'plan' },
|
|
{ label: 'Экстренная', value: 'emergency' },
|
|
]
|
|
|
|
const pad = (value) => String(value).padStart(2, '0')
|
|
const formatLocalDateTime = (timestamp) => {
|
|
const date = new Date(timestamp)
|
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
|
}
|
|
|
|
const loadOperations = async () => {
|
|
if (!props.patient?.department_patient_id) {
|
|
operations.value = []
|
|
return
|
|
}
|
|
|
|
loading.value = true
|
|
try {
|
|
operations.value = await reportStore.getManualPatientOperations(props.patient.department_patient_id)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const loadServiceOptions = useDebounceFn(async (query) => {
|
|
if (!query || query.trim().length < 2) {
|
|
serviceOptions.value = []
|
|
return
|
|
}
|
|
|
|
serviceLoading.value = true
|
|
try {
|
|
const items = await reportStore.searchMedicalServices(query.trim())
|
|
serviceOptions.value = (items ?? []).map((item) => ({
|
|
label: item.label,
|
|
value: item.id,
|
|
code: item.code,
|
|
name: item.name,
|
|
}))
|
|
} finally {
|
|
serviceLoading.value = false
|
|
}
|
|
}, 300)
|
|
|
|
const handleServiceSearch = async (query) => {
|
|
await loadServiceOptions(query)
|
|
}
|
|
|
|
const handleServiceChange = (value, option) => {
|
|
const normalizedId = value === null || value === undefined || value === '' ? null : Number(value)
|
|
form.service_id = Number.isInteger(normalizedId) && normalizedId > 0 ? normalizedId : null
|
|
|
|
if (!option && form.service_id === null) {
|
|
form.service_id = null
|
|
form.service_code = ''
|
|
form.service_name = ''
|
|
return
|
|
}
|
|
|
|
if (!option || Array.isArray(option)) {
|
|
return
|
|
}
|
|
|
|
form.service_code = option.code ?? ''
|
|
form.service_name = option.name ?? ''
|
|
}
|
|
|
|
const toPeriod = (operation) => {
|
|
if (!operation?.startAt || !operation?.endAt) return null
|
|
return [new Date(operation.startAt).getTime(), new Date(operation.endAt).getTime()]
|
|
}
|
|
|
|
const startEdit = (operation) => {
|
|
editingOperationId.value = operation.id
|
|
form.period = toPeriod(operation)
|
|
form.service_id = operation.service?.id ? Number(operation.service.id) : null
|
|
form.service_code = operation.service?.code ?? ''
|
|
form.service_name = operation.service?.name ?? ''
|
|
form.urgency = operation.urgency ?? 'plan'
|
|
|
|
if (operation.service?.label) {
|
|
serviceOptions.value = [{
|
|
label: operation.service.label,
|
|
value: operation.service.id,
|
|
code: operation.service.code,
|
|
name: operation.service.name,
|
|
}]
|
|
}
|
|
}
|
|
|
|
const submit = async () => {
|
|
if (!props.patient?.department_patient_id || submitting.value) return
|
|
|
|
try {
|
|
await formRef.value?.validate()
|
|
} catch {
|
|
return
|
|
}
|
|
|
|
submitting.value = true
|
|
|
|
try {
|
|
const payload = {
|
|
service_id: Number(form.service_id),
|
|
urgency: form.urgency,
|
|
started_at: formatLocalDateTime(form.period[0]),
|
|
ended_at: formatLocalDateTime(form.period[1]),
|
|
}
|
|
|
|
if (editingOperationId.value) {
|
|
await reportStore.updateManualPatientOperation(
|
|
props.patient.department_patient_id,
|
|
editingOperationId.value,
|
|
payload
|
|
)
|
|
window.$message.success('Операция обновлена')
|
|
} else {
|
|
await reportStore.createManualPatientOperation(props.patient.department_patient_id, payload)
|
|
window.$message.success('Операция добавлена')
|
|
}
|
|
|
|
await reportStore.reloadReportPage()
|
|
|
|
await Promise.all([
|
|
loadOperations(),
|
|
props.sourceStatus
|
|
? reportStore.loadPatientsByStatus(props.sourceStatus, { resetPage: true })
|
|
: Promise.resolve(),
|
|
])
|
|
|
|
resetForm()
|
|
} finally {
|
|
submitting.value = false
|
|
}
|
|
}
|
|
|
|
const remove = async (operation) => {
|
|
if (!props.patient?.department_patient_id) return
|
|
|
|
await reportStore.deleteManualPatientOperation(props.patient.department_patient_id, operation.id)
|
|
window.$message.success('Операция удалена')
|
|
|
|
await reportStore.reloadReportPage()
|
|
|
|
await Promise.all([
|
|
loadOperations(),
|
|
props.sourceStatus
|
|
? reportStore.loadPatientsByStatus(props.sourceStatus, { resetPage: true })
|
|
: Promise.resolve(),
|
|
])
|
|
|
|
if (editingOperationId.value === operation.id) {
|
|
resetForm()
|
|
}
|
|
}
|
|
|
|
const columns = computed(() => ([
|
|
{
|
|
title: 'Услуга',
|
|
key: 'service',
|
|
width: 320,
|
|
render: (row) => h(
|
|
NEllipsis,
|
|
{
|
|
tooltip: true,
|
|
},
|
|
{
|
|
default: () => row.service?.label || 'Без услуги',
|
|
}
|
|
),
|
|
},
|
|
{
|
|
title: 'Срочность',
|
|
key: 'urgency',
|
|
width: 110,
|
|
render: (row) => row.urgency === 'emergency' ? 'Э' : 'П',
|
|
},
|
|
{
|
|
title: 'Начало',
|
|
key: 'startAt',
|
|
width: 165,
|
|
render: (row) => row.startAt ? new Date(row.startAt).toLocaleString('ru-RU') : '-',
|
|
},
|
|
{
|
|
title: 'Окончание',
|
|
key: 'endAt',
|
|
width: 165,
|
|
render: (row) => row.endAt ? new Date(row.endAt).toLocaleString('ru-RU') : '-',
|
|
},
|
|
{
|
|
title: 'Мин.',
|
|
key: 'duration',
|
|
width: 80,
|
|
render: (row) => {
|
|
const duration = Number(row.duration)
|
|
return Number.isFinite(duration) ? Math.round(duration) : '-'
|
|
},
|
|
},
|
|
{
|
|
title: '',
|
|
key: 'actions',
|
|
width: 92,
|
|
render: (row) => h(
|
|
'div',
|
|
{ style: { display: 'flex', gap: '8px', justifyContent: 'center' } },
|
|
[
|
|
h(
|
|
NButton,
|
|
{
|
|
quaternary: true,
|
|
size: 'small',
|
|
onClick: () => startEdit(row),
|
|
},
|
|
{
|
|
icon: () => h(NIcon, {}, { default: () => h(TbPencil) }),
|
|
}
|
|
),
|
|
h(
|
|
NPopconfirm,
|
|
{
|
|
onPositiveClick: () => remove(row),
|
|
},
|
|
{
|
|
trigger: () => h(
|
|
NButton,
|
|
{ quaternary: true, type: 'error', size: 'small' },
|
|
{ icon: () => h(NIcon, {}, { default: () => h(TbTrash) }) }
|
|
),
|
|
default: () => 'Удалить операцию?',
|
|
}
|
|
)
|
|
]
|
|
)
|
|
}
|
|
]))
|
|
|
|
watch(() => show.value, async (opened) => {
|
|
if (!opened) {
|
|
resetForm()
|
|
return
|
|
}
|
|
|
|
await loadOperations()
|
|
}, {immediate: false})
|
|
|
|
watch(() => props.patient?.department_patient_id, async () => {
|
|
if (show.value) {
|
|
resetForm()
|
|
await loadOperations()
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<NModal
|
|
v-model:show="show"
|
|
preset="card"
|
|
title="Операции спецконтингента"
|
|
class="max-w-5xl"
|
|
:mask-closable="false"
|
|
>
|
|
<NText class="mb-3 block">
|
|
{{ patient?.fullname }}
|
|
</NText>
|
|
|
|
<NForm ref="formRef" :model="form" :rules="rules" label-placement="top" class="mb-4">
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-3">
|
|
<NFormItem label="Услуга" path="service_id" class="mb-0">
|
|
<NSelect
|
|
v-model:value="form.service_id"
|
|
filterable
|
|
clearable
|
|
remote
|
|
:options="serviceOptions"
|
|
:loading="serviceLoading"
|
|
placeholder="Начните ввод кода или названия услуги"
|
|
@search="handleServiceSearch"
|
|
@update:value="handleServiceChange"
|
|
/>
|
|
</NFormItem>
|
|
<NFormItem label="Срочность" path="urgency" class="mb-0">
|
|
<NSelect
|
|
v-model:value="form.urgency"
|
|
:options="urgencyOptions"
|
|
/>
|
|
</NFormItem>
|
|
<NFormItem label="Начало и окончание" path="period" class="mb-0 md:col-span-1">
|
|
<NDatePicker
|
|
v-model:value="form.period"
|
|
type="datetimerange"
|
|
class="w-full"
|
|
clearable
|
|
/>
|
|
</NFormItem>
|
|
</div>
|
|
|
|
<div class="mt-3 flex items-center gap-2 justify-end">
|
|
<NButton v-if="editingOperationId" @click="resetForm">Отмена редактирования</NButton>
|
|
<NButton type="primary" :loading="submitting" @click="submit">
|
|
{{ editingOperationId ? 'Сохранить изменения' : 'Добавить операцию' }}
|
|
</NButton>
|
|
</div>
|
|
</NForm>
|
|
|
|
<NSpin :show="loading">
|
|
<NDataTable
|
|
:columns="columns"
|
|
:data="operations"
|
|
size="small"
|
|
max-height="320"
|
|
:row-key="(row) => row.id"
|
|
class="text-sm!"
|
|
/>
|
|
</NSpin>
|
|
</NModal>
|
|
</template>
|
|
|
|
<style scoped>
|
|
:deep(.n-data-table-th),
|
|
:deep(.n-data-table-td) {
|
|
font-size: var(--n-font-size);
|
|
}
|
|
</style>
|