208 lines
8.4 KiB
Vue
208 lines
8.4 KiB
Vue
<script setup>
|
|
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 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 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(() => {
|
|
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 showSelectNurseUserModal = ref(false)
|
|
|
|
const openReport = (path) => {
|
|
if (authStore.isDoctor) {
|
|
showSelectUserModal.value = true
|
|
} else {
|
|
router.visit(path)
|
|
}
|
|
}
|
|
|
|
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-center items-center" style="min-height: calc(100vh - 48px);">
|
|
<NFlex vertical :size="10" class="max-w-2xl w-full" style="padding: 0 16px 24px;">
|
|
|
|
<!-- Профиль -->
|
|
<NEl class="profile-card rounded-2xl" style="padding: 18px 22px;">
|
|
<NFlex justify="space-between" align="center" :wrap="false">
|
|
|
|
<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>
|
|
|
|
</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>
|
|
.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>
|