Обновлен стартовый экран
Переписаны запросы для статистики, отчетов Добавлена интеграция отчета сестры
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
<script setup>
|
||||
import {NDatePicker, NIcon, NFlex, NButton, NSkeleton} from 'naive-ui'
|
||||
import {computed, onMounted, ref, watch} from "vue";
|
||||
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, getTime} from "date-fns";
|
||||
import {formatDistanceStrict} from "date-fns";
|
||||
|
||||
const props = defineProps({
|
||||
// Пользователь с ролью админ или зав
|
||||
@@ -16,9 +16,6 @@ const props = defineProps({
|
||||
date: {
|
||||
type: [Number, Array]
|
||||
},
|
||||
isOneDay: {
|
||||
type: Boolean
|
||||
},
|
||||
isShowCurrentDateSwitch: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@@ -27,113 +24,203 @@ const props = defineProps({
|
||||
|
||||
const reportStore = useReportStore()
|
||||
const isLoading = ref(false)
|
||||
const isUseDateRange = computed(() => props.isHeadOrAdmin)
|
||||
const datePicker = ref()
|
||||
const showCalendar = ref(false)
|
||||
const calendarType = ref(props.isHeadOrAdmin ? 'daterange' : 'date')
|
||||
const dateType = computed(() => {
|
||||
if (isUseDateRange.value) return 'daterange'
|
||||
return 'date'
|
||||
})
|
||||
|
||||
const isDateRange = computed(() => calendarType.value === 'daterange')
|
||||
const isDateIsOneDay = computed(() => {
|
||||
return formatDistanceStrict(modelValue.value[0], modelValue.value[1]) === '0 seconds' || formatDistanceStrict(modelValue.value[0], modelValue.value[1]) === '1 day'
|
||||
})
|
||||
|
||||
const queryDate = ref([null, null])
|
||||
const modelValue = defineModel('date')
|
||||
|
||||
const onChangeDate = () => {
|
||||
if (isDateIsOneDay.value) {
|
||||
const startDate = Date.parse(new Date(new Date().getFullYear(), 0, 1).toDateString())
|
||||
const date = Date.parse(new Date().toDateString())
|
||||
const arrayDate = [startDate, date]
|
||||
handleDateUpdate(arrayDate)
|
||||
} else {
|
||||
const date = Date.parse(new Date().toDateString())
|
||||
const arrayDate = [date, date]
|
||||
handleDateUpdate(arrayDate)
|
||||
}
|
||||
|
||||
showCalendar.value = false
|
||||
}
|
||||
|
||||
const setQueryDate = () => {
|
||||
reportStore.openedCollapsible = []
|
||||
router.reload({
|
||||
data: {
|
||||
startAt: queryDate.value[0],
|
||||
endAt: queryDate.value[1]
|
||||
},
|
||||
onFinish: () => {
|
||||
isLoading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
// Всегда используем диапазон, но для врача показываем как один день
|
||||
const calendarType = 'daterange'
|
||||
|
||||
// Форматированное значение для отображения
|
||||
const formattedValue = computed(() => {
|
||||
const value = modelValue.value
|
||||
|
||||
if (props.isHeadOrAdmin) {
|
||||
if (props.isOneDay) {
|
||||
const dateToFormat = Array.isArray(value) ? value[1] : value
|
||||
return formatRussianDate(dateToFormat)
|
||||
} else if (Array.isArray(value) && value.length >= 2 && value[0] && value[1]) { // Для админа - диапазон дат
|
||||
return formatRussianDateRange(value)
|
||||
}
|
||||
|
||||
// Если что-то пошло не так, форматируем как одиночную дату
|
||||
if (value) {
|
||||
const dateToFormat = Array.isArray(value) ? value[0] : value
|
||||
return formatRussianDate(dateToFormat)
|
||||
}
|
||||
|
||||
return ''
|
||||
} else {
|
||||
// Для врача - одиночная дата
|
||||
let dateToFormat
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
dateToFormat = value[1] || value[0]
|
||||
} else {
|
||||
dateToFormat = value
|
||||
}
|
||||
|
||||
// Если выбрана сегодняшняя дата - показываем текущее время
|
||||
if (dateToFormat) {
|
||||
return formatRussianDate(dateToFormat)
|
||||
}
|
||||
if (!value || !Array.isArray(value) || !value[0] || !value[1]) {
|
||||
return ''
|
||||
}
|
||||
|
||||
// Для админа/зав. показываем диапазон
|
||||
if (props.isHeadOrAdmin) {
|
||||
return formatRussianDateRange(value)
|
||||
}
|
||||
|
||||
// Для врача показываем одну дату (конец смены)
|
||||
return formatRussianDate(value[1])
|
||||
})
|
||||
|
||||
const handleDateUpdate = (value) => {
|
||||
isLoading.value = true
|
||||
// Устанавливаем новое значение
|
||||
modelValue.value = value
|
||||
// Проверяем, выбран ли один день
|
||||
const isOneDaySelected = computed(() => {
|
||||
if (!modelValue.value || !Array.isArray(modelValue.value)) return false
|
||||
const [start, end] = modelValue.value
|
||||
if (!start || !end) return false
|
||||
|
||||
// Для диапазона: отправляем только если обе даты заполнены
|
||||
if (isDateRange.value) {
|
||||
if (Array.isArray(value) && value[0] && value[1]) {
|
||||
// Дебаунс для предотвращения двойной отправки
|
||||
setTimeout(() => {
|
||||
// Проверяем что значение не изменилось за время таймаута
|
||||
if (JSON.stringify(modelValue.value) === JSON.stringify(value)) {
|
||||
queryDate.value = value
|
||||
setQueryDate()
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
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 {
|
||||
// Для одиночной даты отправляем сразу
|
||||
if (value) {
|
||||
queryDate.value = [value, value]
|
||||
setQueryDate()
|
||||
}
|
||||
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>
|
||||
@@ -142,30 +229,28 @@ const handleDateUpdate = (value) => {
|
||||
<NSkeleton width="240" height="20" round />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-if="formattedValue" class="font-medium leading-3 cursor-pointer" @click="showCalendar = true">
|
||||
<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"
|
||||
ref="datePicker"
|
||||
class="opacity-0 absolute! inset-x-0 bottom-full -translate-y-1/2"
|
||||
placement="top-start"
|
||||
input-readonly
|
||||
bind-calendar-months
|
||||
@update:value="value => handleDateUpdate(value)"
|
||||
:type="calendarType">
|
||||
<template #confirm="{onConfirm, disabled, text}">
|
||||
<NFlex justify="end" align="center" :size="0">
|
||||
<NButton v-if="isShowCurrentDateSwitch" secondary type="primary" size="tiny" @click="onChangeDate()">
|
||||
{{ isDateIsOneDay ? 'Текущий год' : 'Сегодня' }}
|
||||
</NButton>
|
||||
<NButton type="success" size="tiny" @click="onConfirm" :disabled="disabled">
|
||||
{{ text }}
|
||||
</NButton>
|
||||
</NFlex>
|
||||
</template>
|
||||
<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 />
|
||||
|
||||
Reference in New Issue
Block a user