Files
onboard/resources/js/Components/DatePickerQuery.vue
brusnitsyn 739168d427 Обновлен стартовый экран
Переписаны запросы для статистики, отчетов
Добавлена интеграция отчета сестры
2026-05-28 22:10:00 +09:00

269 lines
8.3 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 {NDatePicker, NIcon, NFlex, NButton, NSkeleton} from 'naive-ui'
import {computed, ref} from "vue";
import {router} from "@inertiajs/vue3";
import {TbCalendar} from 'vue-icons-plus/tb'
import {formatRussianDate, formatRussianDateRange} from "../Utils/dateFormatter.js";
import {useReportStore} from "../Stores/report.js";
import {formatDistanceStrict} from "date-fns";
const props = defineProps({
// Пользователь с ролью админ или зав
isHeadOrAdmin: {
type: Boolean,
default: false
},
date: {
type: [Number, Array]
},
isShowCurrentDateSwitch: {
type: Boolean,
default: false
}
})
const reportStore = useReportStore()
const isLoading = ref(false)
const showCalendar = ref(false)
const modelValue = defineModel('date')
// Всегда используем диапазон, но для врача показываем как один день
const calendarType = 'daterange'
// Форматированное значение для отображения
const formattedValue = computed(() => {
const value = modelValue.value
if (!value || !Array.isArray(value) || !value[0] || !value[1]) {
return ''
}
// Для админа/зав. показываем диапазон
if (props.isHeadOrAdmin) {
return formatRussianDateRange(value)
}
// Для врача показываем одну дату (конец смены)
return formatRussianDate(value[1])
})
// Проверяем, выбран ли один день
const isOneDaySelected = computed(() => {
if (!modelValue.value || !Array.isArray(modelValue.value)) return false
const [start, end] = modelValue.value
if (!start || !end) return false
const diff = formatDistanceStrict(start, end)
return diff === '0 seconds' || diff === '1 day'
})
// Блокировка будущих дат с учетом смен 09:00-09:00
const isDateDisabled = (ts) => {
const date = new Date(ts)
const now = new Date()
// Получаем сегодняшнюю дату без времени
const today = new Date()
today.setHours(0, 0, 0, 0)
// Если выбранная дата меньше сегодня - разрешаем (прошлые даты)
if (date < today) {
return false
}
// Получаем текущий час
const currentHour = now.getHours()
// Если сейчас меньше 09:00, то максимальная дата - сегодня
// Если сейчас 09:00 или больше, то максимальная дата - завтра
const maxDaysOffset = currentHour < 9 ? 0 : 1
const maxDate = new Date()
maxDate.setDate(maxDate.getDate() + maxDaysOffset)
maxDate.setHours(23, 59, 59, 999)
// Блокируем только даты строго больше максимальной
return date > maxDate
}
// Получить начало смены для даты
const getShiftStart = (date) => {
const shiftStart = new Date(date)
shiftStart.setHours(9, 0, 0, 0)
return shiftStart
}
// Получить конец смены для даты
const getShiftEnd = (date) => {
const shiftEnd = new Date(date)
shiftEnd.setHours(9, 0, 0, 0)
shiftEnd.setDate(shiftEnd.getDate() + 1)
return shiftEnd
}
// Получить текущую смену
const getCurrentShift = () => {
const now = new Date()
const today9am = new Date()
today9am.setHours(9, 0, 0, 0)
if (now < today9am) {
const start = new Date(today9am)
start.setDate(start.getDate() - 1)
return [start, today9am]
} else {
const end = new Date(today9am)
end.setDate(end.getDate() + 1)
return [today9am, end]
}
}
// Получить предыдущую смену
const getPreviousShift = () => {
const now = new Date()
const today9am = new Date()
today9am.setHours(9, 0, 0, 0)
if (now < today9am) {
const start = new Date(today9am)
start.setDate(start.getDate() - 2)
const end = new Date(today9am)
end.setDate(end.getDate() - 1)
return [start, end]
} else {
const start = new Date(today9am)
start.setDate(start.getDate() - 1)
return [start, today9am]
}
}
// Шорткаты
const shortcuts = {
'Сегодня': () => getCurrentShift(),
'Вчера': () => getPreviousShift(),
'За 7 дней': () => {
const currentShift = getCurrentShift()
const end = currentShift[1]
const start = new Date(end)
start.setDate(start.getDate() - 7)
start.setHours(9, 0, 0, 0)
return [start, end]
},
'За 30 дней': () => {
const currentShift = getCurrentShift()
const end = currentShift[1]
const start = new Date(end)
start.setDate(start.getDate() - 30)
start.setHours(9, 0, 0, 0)
return [start, end]
},
'Текущий год': () => {
const currentShift = getCurrentShift()
const end = currentShift[1]
const start = new Date(end.getFullYear(), 0, 1)
start.setHours(9, 0, 0, 0)
return [start, end]
}
}
// Обработка изменения даты
const handleDateUpdate = (value) => {
if (!value || !Array.isArray(value) || !value[0] || !value[1]) return
isLoading.value = true
let finalValue = value
// Для врача: если выбран диапазон, преобразуем в один день (берем конец смены)
if (!props.isHeadOrAdmin) {
// Если выбрано несколько дней, берем последний день
finalValue = [value[1], value[1]]
}
modelValue.value = finalValue
// Дебаунс для предотвращения двойной отправки
setTimeout(() => {
if (JSON.stringify(modelValue.value) === JSON.stringify(finalValue)) {
reportStore.openedCollapsible = []
router.reload({
data: {
startAt: finalValue[0],
endAt: finalValue[1]
},
onFinish: () => {
isLoading.value = false
}
})
}
}, 100)
}
// Кнопка "Сегодня/Текущий год"
const onQuickSelect = () => {
const currentShift = getCurrentShift()
if (isOneDaySelected.value) {
// Если выбран один день, переключаем на текущий год
const end = currentShift[1]
const start = new Date(end.getFullYear(), 0, 1)
start.setHours(9, 0, 0, 0)
handleDateUpdate([start, end])
} else {
// Если выбран диапазон, переключаем на сегодня
handleDateUpdate(currentShift)
}
showCalendar.value = false
}
// Текст для кнопки
const quickButtonText = computed(() => {
return isOneDaySelected.value ? 'Текущий год' : 'Сегодня'
})
</script>
<template>
<div class="relative inline-flex items-center">
<template v-if="isLoading">
<NSkeleton width="240" height="20" round />
</template>
<template v-else>
<div
v-if="formattedValue"
class="font-medium leading-3 cursor-pointer"
@click="showCalendar = true"
>
{{ formattedValue }}
</div>
<NDatePicker
v-model:value="modelValue"
v-model:show="showCalendar"
class="opacity-0 absolute! inset-x-0 bottom-full -translate-y-1/2"
placement="top-start"
:shortcuts="shortcuts"
:is-date-disabled="isDateDisabled"
input-readonly
bind-calendar-months
type="daterange"
@update:value="handleDateUpdate"
>
</NDatePicker>
<div class="cursor-pointer p-2 flex items-center justify-center" @click="showCalendar = true">
<NIcon size="20">
<TbCalendar />
</NIcon>
</div>
</template>
</div>
</template>
<style scoped>
:deep(.n-input) {
position: absolute;
pointer-events: none;
}
</style>