Обновлен стартовый экран
Переписаны запросы для статистики, отчетов Добавлена интеграция отчета сестры
This commit is contained in:
@@ -1,45 +1,59 @@
|
||||
<script setup>
|
||||
import { NFlex, NSpace, NDivider, NButton } from 'naive-ui'
|
||||
import ReportSelectDate from "../../Components/ReportSelectDate.vue";
|
||||
import AppUserButton from "./AppUserButton.vue";
|
||||
import {Link} from "@inertiajs/vue3";
|
||||
import AppHeaderRole from "./AppHeaderRole.vue";
|
||||
import {computed, useSlots} from "vue";
|
||||
import { NFlex, NEl, NIcon, NText, NDivider } from 'naive-ui'
|
||||
import { TbHeartRateMonitor } from 'vue-icons-plus/tb'
|
||||
import AppUserButton from "./AppUserButton.vue"
|
||||
import { Link } from "@inertiajs/vue3"
|
||||
import { computed, useSlots } from "vue"
|
||||
import { useThemeVars } from "naive-ui"
|
||||
|
||||
const slots = useSlots()
|
||||
const themeVars = useThemeVars()
|
||||
|
||||
const slots = useSlots()
|
||||
const hasHeaderExtra = computed(() => {
|
||||
if (!slots.headerExtra) return false
|
||||
|
||||
const slotContent = slots.headerExtra()
|
||||
return slotContent && slotContent.length > 0 && slotContent[0].children.length > 0
|
||||
})
|
||||
|
||||
const hasHeaderSuffix = computed(() => {
|
||||
if (!slots.headerExtra) return false
|
||||
|
||||
const slotContent = slots.headerExtra()
|
||||
return slotContent && slotContent.length > 0 && slotContent[0].children.length > 0
|
||||
const content = slots.headerExtra()
|
||||
return content?.length > 0 && content[0].children?.length > 0
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid grid-cols-[auto_1fr_auto] px-4 w-full h-full">
|
||||
<NSpace align="center">
|
||||
<NButton :tag="Link" text href="/">
|
||||
Метрика
|
||||
</NButton>
|
||||
<NDivider v-if="hasHeaderExtra" vertical />
|
||||
<slot name="headerExtra" />
|
||||
</NSpace>
|
||||
<div class="grid grid-cols-[auto_1fr_auto] items-center px-4 w-full h-full">
|
||||
|
||||
<!-- Лого -->
|
||||
<NFlex align="center" :size="10" :wrap="false">
|
||||
<component
|
||||
:is="Link"
|
||||
href="/"
|
||||
style="text-decoration: none; display: flex; align-items: center; gap: 8px;"
|
||||
>
|
||||
<NEl
|
||||
style="
|
||||
width: 28px; height: 28px; border-radius: 8px; flex-shrink: 0;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: color-mix(in srgb, var(--primary-color) 20%, transparent);
|
||||
"
|
||||
>
|
||||
<NIcon size="16" style="color: var(--primary-color);"><TbHeartRateMonitor /></NIcon>
|
||||
</NEl>
|
||||
<NText style="font-size: 14px;">
|
||||
Метрика
|
||||
</NText>
|
||||
</component>
|
||||
|
||||
<template v-if="hasHeaderExtra">
|
||||
<NDivider vertical style="height: 18px;" />
|
||||
<slot name="headerExtra" />
|
||||
</template>
|
||||
</NFlex>
|
||||
|
||||
<!-- Центр (пусто или слот) -->
|
||||
<div></div>
|
||||
<NFlex align="center" justify="end">
|
||||
|
||||
<!-- Пользователь -->
|
||||
<NFlex align="center" justify="end" :wrap="false">
|
||||
<slot name="headerSuffix" />
|
||||
<NDivider v-if="hasHeaderSuffix" vertical />
|
||||
<AppUserButton />
|
||||
</NFlex>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,55 +1,92 @@
|
||||
<script setup>
|
||||
import {useAuthStore} from "../../Stores/auth.js";
|
||||
import {NSelect, NFlex, NText} from 'naive-ui'
|
||||
import {computed} from "vue";
|
||||
import {useForm} from "@inertiajs/vue3";
|
||||
import { useAuthStore } from "../../Stores/auth.js"
|
||||
import { NFlex, NDropdown, NButton, NIcon, NText, NAvatar, NEl } from 'naive-ui'
|
||||
import { TbChevronDown, TbCheck } from 'vue-icons-plus/tb'
|
||||
import { computed, h } from "vue"
|
||||
import { useForm } from "@inertiajs/vue3"
|
||||
import { useThemeVars } from "naive-ui"
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const userOptions = computed(() => {
|
||||
return authStore.availableRoles?.map(itm => {
|
||||
return {
|
||||
label: itm.name,
|
||||
value: itm.role_id
|
||||
}
|
||||
})
|
||||
const themeVars = useThemeVars()
|
||||
|
||||
const initials = computed(() => {
|
||||
const parts = (authStore.user?.name ?? '').trim().split(/\s+/)
|
||||
return parts.slice(0, 2).map(p => p[0]?.toUpperCase() ?? '').join('')
|
||||
})
|
||||
|
||||
const formRole = useForm({
|
||||
role_id: authStore.user.role?.role_id
|
||||
// Если активная роль не задана — берём дефолтную из списка
|
||||
const effectiveRole = computed(() => {
|
||||
if (authStore.user?.role.role_id) return authStore.user.role
|
||||
return authStore.availableRoles?.find(r => r.is_default) ?? authStore.availableRoles?.[0] ?? null
|
||||
})
|
||||
const onChangeRole = (roleId) => {
|
||||
|
||||
const currentRoleId = computed(() => effectiveRole.value?.role_id ?? null)
|
||||
const currentRoleName = computed(() => effectiveRole.value?.name ?? '')
|
||||
const hasMultipleRoles = computed(() => (authStore.availableRoles?.length ?? 0) > 1)
|
||||
|
||||
const roleOptions = computed(() =>
|
||||
authStore.availableRoles?.map(r => ({
|
||||
label: r.name,
|
||||
key: r.role_id,
|
||||
icon: () => h(NIcon, {
|
||||
size: 14,
|
||||
style: r.role_id === currentRoleId.value ? '' : 'opacity: 0; pointer-events: none;',
|
||||
}, () => h(TbCheck)),
|
||||
})) ?? []
|
||||
)
|
||||
|
||||
const formRole = useForm({ role_id: null })
|
||||
|
||||
const onSelectRole = (roleId) => {
|
||||
if (roleId === currentRoleId.value) return
|
||||
formRole.role_id = roleId
|
||||
formRole.post('/user/role/change')
|
||||
}
|
||||
|
||||
const themeOverride = {
|
||||
border: null,
|
||||
borderHover: null,
|
||||
borderPressed: null,
|
||||
borderFocus: null,
|
||||
paddingMedium: null,
|
||||
rippleColor: null
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NFlex align="center" :wrap="false">
|
||||
<div class="min-w-[220px]">
|
||||
<NSelect :options="userOptions"
|
||||
v-model:value="formRole.role_id"
|
||||
@update:value="value => onChangeRole(value)"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{{ authStore.user?.name }}
|
||||
</div>
|
||||
<NFlex align="center" :size="12" :wrap="false">
|
||||
|
||||
<!-- Переключатель роли -->
|
||||
<NDropdown
|
||||
v-if="hasMultipleRoles"
|
||||
:options="roleOptions"
|
||||
placement="bottom-end"
|
||||
@select="onSelectRole"
|
||||
>
|
||||
<NButton
|
||||
text
|
||||
size="small"
|
||||
icon-placement="right"
|
||||
:loading="formRole.processing"
|
||||
>
|
||||
{{ currentRoleName }}
|
||||
<template #icon>
|
||||
<NIcon size="12"><TbChevronDown /></NIcon>
|
||||
</template>
|
||||
</NButton>
|
||||
</NDropdown>
|
||||
|
||||
<NText v-else-if="currentRoleName" style="font-size: 13px;">
|
||||
{{ currentRoleName }}
|
||||
</NText>
|
||||
|
||||
<!-- Разделитель -->
|
||||
<NEl :style="`width: 1px; height: 18px; background: ${themeVars.dividerColor};`" />
|
||||
|
||||
<!-- Аватар + имя -->
|
||||
<NFlex align="center" :size="8" :wrap="false">
|
||||
<NAvatar
|
||||
round :size="28"
|
||||
style="
|
||||
color: var(--primary-color);
|
||||
font-size: 11px; font-weight: 700; flex-shrink: 0;
|
||||
"
|
||||
>{{ initials }}</NAvatar>
|
||||
<NText style="white-space: nowrap; max-width: 180px; overflow: hidden; text-overflow: ellipsis;">
|
||||
{{ authStore.user?.name }}
|
||||
</NText>
|
||||
</NFlex>
|
||||
|
||||
</NFlex>
|
||||
<!-- <NDropdown :options="userOptions" placement="bottom-end">-->
|
||||
<!-- <NButton :theme-overrides="themeOverride">-->
|
||||
<!-- {{ authStore.user?.name }}-->
|
||||
<!-- </NButton>-->
|
||||
<!-- </NDropdown>-->
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
import {NDatePicker} from 'naive-ui'
|
||||
const onUpdateDateQuery = (value) => {
|
||||
axios.get(`/api/metric-forms/1/report-by-date?sent_at=${value}`)
|
||||
.then(res => {
|
||||
console.log(res)
|
||||
})
|
||||
.then(() => {})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user