Обновлен стартовый экран
Переписаны запросы для статистики, отчетов Добавлена интеграция отчета сестры
This commit is contained in:
@@ -1,102 +1,207 @@
|
||||
<script setup>
|
||||
import AppLayout from "../Layouts/AppLayout.vue";
|
||||
import { useAuthStore } from "../Stores/auth.js";
|
||||
import { NH1, NSpace, NP, NFlex } from 'naive-ui'
|
||||
import StartButton from "../Components/StartButton.vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { format, getHours } from "date-fns";
|
||||
import { ru } from "date-fns/locale";
|
||||
import { useNow } from "@vueuse/core";
|
||||
import { TbArticle, TbChartTreemap, TbDoorExit, TbUserCog } from "vue-icons-plus/tb";
|
||||
import { FcWikipedia } from "vue-icons-plus/fc";
|
||||
import { useReportStore } from "../Stores/report.js";
|
||||
import SelectUserModal from "./Report/Components/SelectUserModal.vue";
|
||||
import { Link } from "@inertiajs/vue3";
|
||||
import { useServerTime } from "../Composables/useServerTime.js";
|
||||
import AppLayout from "../Layouts/AppLayout.vue"
|
||||
import { useAuthStore } from "../Stores/auth.js"
|
||||
import { NEl, NFlex, NTag, NText, NAvatar, NIcon } from 'naive-ui'
|
||||
import ActionTile from "../Components/ActionTile.vue"
|
||||
import { computed, ref } from "vue"
|
||||
import { format, getHours } from "date-fns"
|
||||
import { ru } from "date-fns/locale"
|
||||
import { useNow } from "@vueuse/core"
|
||||
import { TbArticle, TbChartTreemap, TbDoorExit, TbUserCog, TbStethoscope } from "vue-icons-plus/tb"
|
||||
import SelectUserModal from "./Report/Components/SelectUserModal.vue"
|
||||
import { Link, router } from "@inertiajs/vue3"
|
||||
import { useServerTime } from "../Composables/useServerTime.js"
|
||||
import { useThemeVars } from "naive-ui"
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const reportStore = useReportStore()
|
||||
const themeVars = useThemeVars()
|
||||
|
||||
const { serverTime } = useServerTime()
|
||||
const now = useNow({ interval: 1000 })
|
||||
|
||||
const timeSource = computed(() => {
|
||||
if (serverTime.value && !isNaN(serverTime.value.getTime())) return serverTime.value
|
||||
return now.value
|
||||
})
|
||||
|
||||
const currentTime = computed(() => format(timeSource.value, 'HH:mm:ss'))
|
||||
const currentDate = computed(() => {
|
||||
const nowTime = serverTime.value
|
||||
|
||||
// Если дата ещё не пришла или невалидна - возвращаем заглушку
|
||||
if (!serverTime.value || isNaN(serverTime.value.getTime())) {
|
||||
const formatted = format(now.value, 'PPPPpp', { locale: ru })
|
||||
return formatted.charAt(0).toUpperCase() + formatted.slice(1)
|
||||
}
|
||||
|
||||
const formatted = format(nowTime, 'PPPPpp', { locale: ru })
|
||||
const formatted = format(timeSource.value, 'EEEE, d MMMM', { locale: ru })
|
||||
return formatted.charAt(0).toUpperCase() + formatted.slice(1)
|
||||
})
|
||||
|
||||
const greeting = computed(() => {
|
||||
const h = getHours(timeSource.value)
|
||||
if (h >= 5 && h < 12) return 'Доброе утро'
|
||||
if (h >= 12 && h < 18) return 'Добрый день'
|
||||
if (h >= 18 && h < 23) return 'Добрый вечер'
|
||||
return 'Доброй ночи'
|
||||
})
|
||||
|
||||
const initials = computed(() => {
|
||||
const parts = (authStore.user?.name ?? '').trim().split(/\s+/)
|
||||
return parts.slice(0, 2).map(p => p[0]?.toUpperCase() ?? '').join('')
|
||||
})
|
||||
|
||||
const roleTagTypes = {
|
||||
admin: 'error', gv: 'warning', zam: 'warning',
|
||||
zav: 'info', dej: 'primary', nurse: 'success',
|
||||
}
|
||||
|
||||
const roleName = computed(() => authStore.user?.role.name ?? '')
|
||||
const roleColor = computed(() => roleTagTypes[authStore.role?.slug] ?? 'default')
|
||||
const deptName = computed(() => authStore.userDepartment?.name_full ?? null)
|
||||
|
||||
const isLockReportButton = computed(() => {
|
||||
const hasPrivilegedRole = authStore.availableRoles?.some(role =>
|
||||
['admin', 'head_of_department'].includes(role.slug)
|
||||
)
|
||||
|
||||
if (hasPrivilegedRole) return false
|
||||
|
||||
if (!serverTime.value || isNaN(serverTime.value.getTime())) {
|
||||
return true
|
||||
}
|
||||
|
||||
const hours = getHours(serverTime.value)
|
||||
|
||||
if (hours >= 6 && hours < 7) return false
|
||||
|
||||
return true
|
||||
if (authStore.isSeniorStaff) return false
|
||||
if (!serverTime.value || isNaN(serverTime.value.getTime())) return true
|
||||
return getHours(serverTime.value) < 6 || getHours(serverTime.value) >= 7
|
||||
})
|
||||
|
||||
const showSelectUserModal = ref(false)
|
||||
const onShowSelectUserModal = () => {
|
||||
if (authStore.isDoctor)
|
||||
const showSelectNurseUserModal = ref(false)
|
||||
|
||||
const openReport = (path) => {
|
||||
if (authStore.isDoctor) {
|
||||
showSelectUserModal.value = true
|
||||
} else {
|
||||
router.visit(path)
|
||||
}
|
||||
}
|
||||
|
||||
const reportButtonType = computed(() => authStore.isDoctor ? 'button' : Link)
|
||||
const openNurseReport = () => {
|
||||
if (authStore.isSeniorStaff) {
|
||||
showSelectNurseUserModal.value = true
|
||||
} else {
|
||||
router.visit('/nurse/report')
|
||||
}
|
||||
}
|
||||
|
||||
const cardColor = computed(() => themeVars.value.cardColor)
|
||||
const dividerColor = computed(() => themeVars.value.dividerColor)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout>
|
||||
<div class="flex flex-col justify-start items-center mt-12 max-h-[calc(100vh-96px)] min-h-[calc(100vh-96px)] h-full">
|
||||
<NFlex vertical justify="space-between" class="max-w-xl w-full h-full mb-12">
|
||||
<NFlex vertical align="center" justify="center" class="max-w-xl w-full">
|
||||
<NSpace vertical align="center">
|
||||
<NH1 class="mb-0! text-center leading-9">
|
||||
Здравствуйте<br>{{ authStore.user.name }}!
|
||||
</NH1>
|
||||
<NP class="mb-4!">
|
||||
{{ currentDate }}
|
||||
</NP>
|
||||
</NSpace>
|
||||
<div class="flex flex-col justify-center items-center" style="min-height: calc(100vh - 48px);">
|
||||
<NFlex vertical :size="10" class="max-w-2xl w-full" style="padding: 0 16px 24px;">
|
||||
|
||||
<StartButton v-if="authStore.isAdmin || authStore.isDoctor" title="Заполнить сводную"
|
||||
description="Заполняется ежедневно c 06:00 по 07:00" href="/report" :tag="reportButtonType"
|
||||
@click="onShowSelectUserModal" :icon="TbArticle" :lock="isLockReportButton"
|
||||
lock-message="Будет доступно c 06:00 по 07:00" />
|
||||
<StartButton v-if="authStore.isAdmin || authStore.isHeadOfDepartment"
|
||||
title="Статистика моего отделения"
|
||||
:description="`Ваш профиль в системе: ${authStore.userDepartment.department_type.name_short}`"
|
||||
:href="`/statistic`" :icon="TbChartTreemap" />
|
||||
<StartButton v-if="authStore.isAdmin" title="Панель администратора"
|
||||
description="Управление приложением" href="/admin" :tag="Link" :icon="TbUserCog" />
|
||||
<StartButton title="Документация"
|
||||
description="Правила работы с системой"
|
||||
lock lock-message="В разработке"
|
||||
:href="`/statistic`" :icon="FcWikipedia" />
|
||||
<!-- Профиль -->
|
||||
<NEl class="profile-card rounded-2xl" style="padding: 18px 22px;">
|
||||
<NFlex justify="space-between" align="center" :wrap="false">
|
||||
|
||||
</NFlex>
|
||||
<NFlex align="center" :size="12" :wrap="false" style="min-width: 0; flex: 1;">
|
||||
<NAvatar
|
||||
round :size="46"
|
||||
style="
|
||||
background: color-mix(in srgb, var(--primary-color) 22%, transparent);
|
||||
color: var(--primary-color);
|
||||
font-size: 17px; font-weight: 700; flex-shrink: 0;
|
||||
"
|
||||
>{{ initials }}</NAvatar>
|
||||
|
||||
<div style="min-width: 0;">
|
||||
<NText depth="3" style="font-size: 12px; display: block; margin-bottom: 2px; line-height: 1;">
|
||||
{{ greeting }}
|
||||
</NText>
|
||||
<NText style="font-size: 15px; font-weight: 600; display: block; line-height: 1.25; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
||||
{{ authStore.user?.name }}
|
||||
</NText>
|
||||
<NFlex align="center" :size="6" style="margin-top: 6px;" :wrap="false">
|
||||
<NTag :type="roleColor" size="small" round :bordered="false">{{ roleName }}</NTag>
|
||||
<template v-if="deptName">
|
||||
<NEl style="width: 3px; height: 3px; border-radius: 50%; background: currentColor; opacity: .3; flex-shrink: 0;" />
|
||||
<NText depth="3" style="font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">{{ deptName }}</NText>
|
||||
</template>
|
||||
</NFlex>
|
||||
</div>
|
||||
</NFlex>
|
||||
|
||||
<div style="text-align: right; flex-shrink: 0; margin-left: 16px;">
|
||||
<NText style="font-size: 26px; font-weight: 700; font-variant-numeric: tabular-nums; display: block; letter-spacing: -1px; line-height: 1.05;">
|
||||
{{ currentTime }}
|
||||
</NText>
|
||||
<NText depth="3" style="font-size: 11px; display: block; margin-top: 4px; white-space: nowrap;">
|
||||
{{ currentDate }}
|
||||
</NText>
|
||||
</div>
|
||||
|
||||
</NFlex>
|
||||
</NEl>
|
||||
|
||||
<!-- Действия -->
|
||||
<div class="bento-grid">
|
||||
<ActionTile
|
||||
v-if="authStore.hasPermission('report.create')"
|
||||
:icon="TbArticle"
|
||||
title="Заполнить сводную"
|
||||
description="Ежедневный отчёт врача"
|
||||
@click="openReport('/report')"
|
||||
/>
|
||||
<ActionTile
|
||||
v-if="authStore.hasPermission('nurse.report.view')"
|
||||
:icon="TbStethoscope"
|
||||
title="Журнал пациентов"
|
||||
description="Записи мед. сестры"
|
||||
@click="openNurseReport"
|
||||
/>
|
||||
<ActionTile
|
||||
v-if="authStore.hasPermission('stats.view')"
|
||||
:icon="TbChartTreemap"
|
||||
title="Статистика"
|
||||
:description="deptName || 'Просмотр данных'"
|
||||
:tag="Link"
|
||||
href="/statistic"
|
||||
/>
|
||||
<ActionTile
|
||||
v-if="authStore.hasPermission('users.manage')"
|
||||
:icon="TbUserCog"
|
||||
title="Администрирование"
|
||||
description="Управление системой"
|
||||
:tag="Link"
|
||||
href="/admin"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Выход -->
|
||||
<a href="/logout" class="exit-link">
|
||||
<NFlex align="center" justify="center" :size="6">
|
||||
<NIcon size="15"><TbDoorExit /></NIcon>
|
||||
<NText style="font-size: 13px;">Выйти из системы</NText>
|
||||
</NFlex>
|
||||
</a>
|
||||
|
||||
<StartButton title="Выйти из системы" description="Завершение работы с текущей учетной записью"
|
||||
href="/logout" :icon="TbDoorExit" />
|
||||
</NFlex>
|
||||
</div>
|
||||
|
||||
<SelectUserModal v-model:show="showSelectUserModal" />
|
||||
<SelectUserModal
|
||||
v-model:show="showSelectNurseUserModal"
|
||||
target-path="/nurse/report"
|
||||
submit-label="Перейти к журналу пациентов"
|
||||
:with-exists-check="false"
|
||||
/>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
.profile-card {
|
||||
background: v-bind(cardColor);
|
||||
border: 1px solid v-bind(dividerColor);
|
||||
}
|
||||
|
||||
.bento-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.exit-link {
|
||||
text-decoration: none;
|
||||
opacity: 0.45;
|
||||
transition: opacity .15s;
|
||||
}
|
||||
.exit-link:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user